diff --git a/.gn b/.gn index 0eaf794..8954741 100644 --- a/.gn +++ b/.gn
@@ -66,7 +66,6 @@ "//extensions:*", # 28 errors "//headless:*", # 107 errors "//ppapi/proxy:ipc_sources", # 13 errors - "//ppapi/thunk:*", # 1071 errors "//remoting/host/security_key:*", # 10 errors "//remoting/host/win:*", # 43 errors "//sandbox/win:*", # 7 errors
diff --git a/DEPS b/DEPS index 0aded74..98c7711 100644 --- a/DEPS +++ b/DEPS
@@ -228,7 +228,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': '9cd9d0f3de06099dff988c4f9abdcd5eb61633c3', + 'skia_revision': '2cbb3f55ea7bbb4d0f0c6f13593f534a499b9285', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. @@ -240,15 +240,15 @@ # 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': '1f0e2916e59c47bbc475444928d139261183474c', + 'angle_revision': 'f99426400493a4623d55d59a9c51cfee87b30bbf', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. - 'swiftshader_revision': '1bc8669c8b787df9d30e954a3300d925d581d38a', + 'swiftshader_revision': 'd4483095765ecd7914fbc90dc254a1f98565a915', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. - 'pdfium_revision': 'fdbfb3c44434f3de59d2e23a8dbfeefe52608167', + 'pdfium_revision': 'c716d47fba24d8da451d3db291ede1eb9d596bcb', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling BoringSSL # and whatever else without interference from each other. @@ -275,7 +275,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling NaCl # and whatever else without interference from each other. - 'nacl_revision': 'a2161789b39b6811ba2a8c64a2341b0a78d25f79', + 'nacl_revision': '55656f56c1e0dfdc73ff624f002257c346be8587', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling freetype # and whatever else without interference from each other. @@ -347,7 +347,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'dawn_revision': '8e957160b1540712f996266595b8eb11458f239a', + 'dawn_revision': '8ec325cfc61e727e3b791e5520f52d5d9b826e50', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -767,7 +767,7 @@ 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': 'yBqUm9V81rpvY4pY_cgtkc_eCKPE0Imj3MnzzGoWliwC', + 'version': 'Er2u3dWX5_7MBWu-tE6iYNOJSmn3SOqDuD-0R0c-o2YC', }, ], 'condition': 'checkout_android', @@ -1621,7 +1621,7 @@ Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '26da66e2987afd8fb426c8b83f7b96d500c3fb01', 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + '49c9b4ec3748a7f8e37865f44a89c1be8ed6dd8c', + Var('webrtc_git') + '/src.git' + '@' + '5654a1395036d40b9ef40f01f0bf985f41b4f0a1', 'src/third_party/libgifcodec': Var('skia_git') + '/libgifcodec' + '@'+ Var('libgifcodec_revision'), @@ -1682,7 +1682,7 @@ Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'), 'src-internal': { - 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@1097ffdceae6ff5ef5da4fbaec9187c1a9934586', + 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3ecbeba2cd223e670bde8609e94537c5dded0d30', 'condition': 'checkout_src_internal', },
diff --git a/android_webview/browser/aw_quota_manager_bridge.cc b/android_webview/browser/aw_quota_manager_bridge.cc index 8d7c51e..87d3dbe 100644 --- a/android_webview/browser/aw_quota_manager_bridge.cc +++ b/android_webview/browser/aw_quota_manager_bridge.cc
@@ -52,8 +52,8 @@ friend class base::RefCountedThreadSafe<GetStorageKeysTask>; ~GetStorageKeysTask(); - void OnStorageKeysObtained(const std::set<blink::StorageKey>& storage_keys, - blink::mojom::StorageType type); + void OnStorageKeysObtained(blink::mojom::StorageType type, + const std::set<blink::StorageKey>& storage_keys); void OnUsageAndQuotaObtained(const blink::StorageKey& storage_key, blink::mojom::QuotaStatusCode status_code, @@ -90,16 +90,15 @@ content::GetIOThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce( - &QuotaManager::GetStorageKeysModifiedBetween, quota_manager_, + &QuotaManager::GetStorageKeysForType, quota_manager_, blink::mojom::StorageType::kTemporary, - base::Time() /* Since beginning of time. */, - base::Time::Max() /* Until the end of time. */, - base::BindOnce(&GetStorageKeysTask::OnStorageKeysObtained, this))); + base::BindOnce(&GetStorageKeysTask::OnStorageKeysObtained, this, + blink::mojom::StorageType::kTemporary))); } void GetStorageKeysTask::OnStorageKeysObtained( - const std::set<blink::StorageKey>& storage_keys, - blink::mojom::StorageType type) { + blink::mojom::StorageType type, + const std::set<blink::StorageKey>& storage_keys) { DCHECK_CURRENTLY_ON(BrowserThread::IO); num_callbacks_to_wait_ = storage_keys.size(); num_callbacks_received_ = 0u;
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc index db6573d..b6f555e 100644 --- a/ash/accelerators/accelerator_controller_impl.cc +++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -1583,6 +1583,13 @@ return accelerator_history_.get(); } +bool AcceleratorControllerImpl::DoesAcceleratorMatchAction( + const ui::Accelerator& accelerator, + AcceleratorAction action) { + AcceleratorAction* action_ptr = accelerators_.Find(accelerator); + return action_ptr && *action_ptr == action; +} + bool AcceleratorControllerImpl::IsPreferred( const ui::Accelerator& accelerator) const { const AcceleratorAction* action_ptr = accelerators_.Find(accelerator);
diff --git a/ash/accelerators/accelerator_controller_impl.h b/ash/accelerators/accelerator_controller_impl.h index d68bd0ff..24dbbe1 100644 --- a/ash/accelerators/accelerator_controller_impl.h +++ b/ash/accelerators/accelerator_controller_impl.h
@@ -207,6 +207,8 @@ bool OnMenuAccelerator(const ui::Accelerator& accelerator) override; bool IsRegistered(const ui::Accelerator& accelerator) const override; AcceleratorHistoryImpl* GetAcceleratorHistory() override; + bool DoesAcceleratorMatchAction(const ui::Accelerator& accelerator, + AcceleratorAction action) override; // Returns true if the |accelerator| is preferred. A preferred accelerator // is handled before being passed to an window/web contents, unless
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc index 8ce6beb..b7a7a4a 100644 --- a/ash/accelerators/accelerator_controller_unittest.cc +++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -39,6 +39,8 @@ #include "ash/system/brightness_control_delegate.h" #include "ash/system/keyboard_brightness_control_delegate.h" #include "ash/system/power/power_button_controller_test_api.h" +#include "ash/system/status_area_widget_test_helper.h" +#include "ash/system/unified/unified_system_tray.h" #include "ash/test/ash_test_base.h" #include "ash/test_media_client.h" #include "ash/wm/lock_state_controller.h" @@ -1308,6 +1310,23 @@ GetAppListTestHelper()->CheckVisibility(true); } +TEST_F(AcceleratorControllerTest, GlobalAcceleratorsToggleQuickSettings) { + UnifiedSystemTray* tray = + StatusAreaWidgetTestHelper::GetStatusAreaWidget()->unified_system_tray(); + + auto* generator = GetEventGenerator(); + + // Pressing accelerator once should show the quick settings bubble. + generator->PressKey(ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(tray->IsBubbleShown()); + + // Pressing accelerator a second time should dismiss the bubble. + generator->PressKey(ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(tray->IsBubbleShown()); +} + TEST_F(AcceleratorControllerTest, GlobalAcceleratorsToggleAppListFullscreen) { base::HistogramTester histogram_tester;
diff --git a/ash/display/display_move_window_util_unittest.cc b/ash/display/display_move_window_util_unittest.cc index eaf3e8b..9039b7a 100644 --- a/ash/display/display_move_window_util_unittest.cc +++ b/ash/display/display_move_window_util_unittest.cc
@@ -140,7 +140,7 @@ EXPECT_EQ(GetDefaultLeftSnappedBoundsInDisplay( screen->GetDisplayNearestWindow(window)), window->GetBoundsInScreen()); - EXPECT_EQ(0.5f, *window_state->snapped_width_ratio()); + EXPECT_EQ(0.5f, *window_state->snap_ratio()); PerformMoveWindowAccel(); EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), screen->GetDisplayNearestWindow(window).id()); @@ -149,7 +149,7 @@ EXPECT_EQ(GetDefaultLeftSnappedBoundsInDisplay( screen->GetDisplayNearestWindow(window)), window->GetBoundsInScreen()); - EXPECT_EQ(0.5f, *window_state->snapped_width_ratio()); + EXPECT_EQ(0.5f, *window_state->snap_ratio()); } // Tests that movement follows cycling through sorted display id list.
diff --git a/ash/public/cpp/accelerators.h b/ash/public/cpp/accelerators.h index 9a6b2c4..bd57004 100644 --- a/ash/public/cpp/accelerators.h +++ b/ash/public/cpp/accelerators.h
@@ -223,6 +223,11 @@ // Returns the accelerator histotry. virtual AcceleratorHistory* GetAcceleratorHistory() = 0; + // Returns true if the provided accelerator matches the provided accelerator + // action. + virtual bool DoesAcceleratorMatchAction(const ui::Accelerator& accelerator, + const AcceleratorAction action) = 0; + protected: AcceleratorController(); virtual ~AcceleratorController();
diff --git a/ash/public/cpp/wallpaper/wallpaper_controller.h b/ash/public/cpp/wallpaper/wallpaper_controller.h index 49684c6..7996110 100644 --- a/ash/public/cpp/wallpaper/wallpaper_controller.h +++ b/ash/public/cpp/wallpaper/wallpaper_controller.h
@@ -49,7 +49,6 @@ // Sets wallpaper from a local file and updates the saved wallpaper info for // the user. // |account_id|: The user's account id. - // |wallpaper_files_id|: The file id for |account_id|. // |file_path|: The path of the image file to read. // |layout|: The layout of the wallpaper, used for wallpaper resizing. // |preview_mode|: If true, show the wallpaper immediately but doesn't change @@ -58,7 +57,6 @@ // |callback|: called when the image is read from file and decoded. using SetCustomWallpaperCallback = base::OnceCallback<void(bool success)>; virtual void SetCustomWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const base::FilePath& file_path, WallpaperLayout layout, bool preview_mode, @@ -67,7 +65,6 @@ // Sets wallpaper from a local file and updates the saved wallpaper info for // the user. // |account_id|: The user's account id. - // |wallpaper_files_id|: The file id for |account_id|. // |file_name|: The name of the wallpaper file. // |layout|: The layout of the wallpaper, used for wallpaper resizing. // |image|: The wallpaper image. @@ -75,7 +72,6 @@ // the user wallpaper info until |ConfirmPreviewWallpaper| is // called. virtual void SetCustomWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, WallpaperLayout layout, const gfx::ImageSkia& image, @@ -121,11 +117,9 @@ // Sets the user's wallpaper to be the default wallpaper. Note: different user // types may have different default wallpapers. // |account_id|: The user's account id. - // |wallpaper_files_id|: The file id for |account_id|. // |show_wallpaper|: If false, don't show the new wallpaper now but only // update cache. virtual void SetDefaultWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, bool show_wallpaper) = 0; // Sets the paths of the customized default wallpaper to be used wherever a @@ -145,10 +139,8 @@ // next time |ShowUserWallpaper| is called. Note: it is different from device // policy. // |account_id|: The user's account id. - // |wallpaper_files_id|: The file id for |account_id|. // |data|: The data used to decode the image. virtual void SetPolicyWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& data) = 0; // Sets the path of device policy wallpaper. @@ -169,7 +161,6 @@ // 1) the user is not permitted to change wallpaper, or // 2) updating the on-screen wallpaper is not allowed at the given moment. virtual bool SetThirdPartyWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, WallpaperLayout layout, const gfx::ImageSkia& image) = 0; @@ -220,9 +211,7 @@ // Removes all of the user's saved wallpapers and related info. // |account_id|: The user's account id. - // |wallpaper_files_id|: The file id for |account_id|. - virtual void RemoveUserWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id) = 0; + virtual void RemoveUserWallpaper(const AccountId& account_id) = 0; // Removes all of the user's saved wallpapers and related info if the // wallpaper was set by |SetPolicyWallpaper|. In addition, sets the user's @@ -230,9 +219,7 @@ // wallpaper immediately, otherwise, the default wallpaper will be shown the // next time |ShowUserWallpaper| is called. // |account_id|: The user's account id. - // |wallpaper_files_id|: The file id for |account_id|. - virtual void RemovePolicyWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id) = 0; + virtual void RemovePolicyWallpaper(const AccountId& account_id) = 0; // Returns the urls of the wallpapers that exist in local file system (i.e. // |SetOnlineWallpaperFromData| was called earlier). The url is used as id
diff --git a/ash/public/cpp/wallpaper/wallpaper_controller_client.h b/ash/public/cpp/wallpaper/wallpaper_controller_client.h index 4e45be4..6c267aa 100644 --- a/ash/public/cpp/wallpaper/wallpaper_controller_client.h +++ b/ash/public/cpp/wallpaper/wallpaper_controller_client.h
@@ -46,6 +46,10 @@ // Returns true if image was successfully saved. virtual bool SaveWallpaperToDriveFs(const AccountId& account_id, const base::FilePath& origin) = 0; + + virtual void GetFilesId( + const AccountId& account_id, + base::OnceCallback<void(const std::string&)> files_id_callback) const = 0; }; } // namespace ash
diff --git a/ash/system/tray/tray_bubble_view.cc b/ash/system/tray/tray_bubble_view.cc index c6b9e255..7e0e49d 100644 --- a/ash/system/tray/tray_bubble_view.cc +++ b/ash/system/tray/tray_bubble_view.cc
@@ -7,7 +7,9 @@ #include <algorithm> #include <numeric> +#include "ash/accelerators/accelerator_controller_impl.h" #include "ash/accessibility/accessibility_controller_impl.h" +#include "ash/public/cpp/accelerators.h" #include "ash/shell.h" #include "ash/style/ash_color_provider.h" #include "ash/system/tray/tray_constants.h" @@ -20,6 +22,7 @@ #include "ui/accessibility/ax_node_data.h" #include "ui/aura/env.h" #include "ui/aura/window.h" +#include "ui/base/accelerators/accelerator.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/compositor/layer.h" #include "ui/compositor/layer_type.h" @@ -141,6 +144,13 @@ void TrayBubbleView::Delegate::HideBubble(const TrayBubbleView* bubble_view) {} +absl::optional<AcceleratorAction> +TrayBubbleView::Delegate::GetAcceleratorAction() const { + // TODO(crbug/1234891) Make this a pure virtual function so all + // bubble delegates need to specify accelerator actions. + return absl::nullopt; +} + TrayBubbleView::InitParams::InitParams() = default; TrayBubbleView::InitParams::InitParams(const InitParams& other) = default; @@ -203,6 +213,18 @@ ViewsDelegate::ProcessMenuAcceleratorResult result = ViewsDelegate::GetInstance()->ProcessAcceleratorWhileMenuShowing( accelerator); + + // crbug/1212857 Do not manually close the bubble if the accelerator action + // is going to do it. The accelerator action is executed asynchronously + // because the accelerator may have to destroy the bubble view. So closing the + // bubble below will result in the accelerator action reopening the bubble. + if (tray_bubble_view_->GetAcceleratorAction().has_value() && + AcceleratorControllerImpl::Get()->DoesAcceleratorMatchAction( + ui::Accelerator(*event), + tray_bubble_view_->GetAcceleratorAction().value())) { + return; + } + if (result == ViewsDelegate::ProcessMenuAcceleratorResult::CLOSE_MENU) tray_bubble_view_->CloseBubbleView(); } @@ -324,6 +346,10 @@ return bubble_border_ ? bubble_border_->GetInsets() : gfx::Insets(); } +absl::optional<AcceleratorAction> TrayBubbleView::GetAcceleratorAction() const { + return delegate_->GetAcceleratorAction(); +} + void TrayBubbleView::ResetDelegate() { reroute_event_handler_.reset();
diff --git a/ash/system/tray/tray_bubble_view.h b/ash/system/tray/tray_bubble_view.h index da80d38..5690ee8b 100644 --- a/ash/system/tray/tray_bubble_view.h +++ b/ash/system/tray/tray_bubble_view.h
@@ -8,6 +8,7 @@ #include <memory> #include "ash/ash_export.h" +#include "ash/public/cpp/accelerators.h" #include "ash/public/cpp/shelf_types.h" #include "ash/system/status_area_widget.h" #include "base/macros.h" @@ -67,6 +68,10 @@ // child view was closed). virtual void HideBubble(const TrayBubbleView* bubble_view); + // Returns the accelerator action associated with the delegate's bubble + // view. + virtual absl::optional<AcceleratorAction> GetAcceleratorAction() const; + private: DISALLOW_COPY_AND_ASSIGN(Delegate); }; @@ -129,6 +134,9 @@ // Returns the border insets. Called by TrayEventFilter. gfx::Insets GetBorderInsets() const; + // Returns the accelerator action associated with this bubble view. + absl::optional<AcceleratorAction> GetAcceleratorAction() const; + // Called when the delegate is destroyed. This must be called before the // delegate is actually destroyed. TrayBubbleView will do clean up in // ResetDelegate.
diff --git a/ash/system/unified/unified_system_tray.cc b/ash/system/unified/unified_system_tray.cc index c8dd06a..207a7be 100644 --- a/ash/system/unified/unified_system_tray.cc +++ b/ash/system/unified/unified_system_tray.cc
@@ -349,6 +349,11 @@ return "UnifiedSystemTray"; } +absl::optional<AcceleratorAction> UnifiedSystemTray::GetAcceleratorAction() + const { + return absl::make_optional(TOGGLE_SYSTEM_TRAY_BUBBLE); +} + void UnifiedSystemTray::OnShelfConfigUpdated() { // Ensure the margin is updated correctly depending on whether dense shelf // is currently shown or not.
diff --git a/ash/system/unified/unified_system_tray.h b/ash/system/unified/unified_system_tray.h index 18b3a10..6cab5ae 100644 --- a/ash/system/unified/unified_system_tray.h +++ b/ash/system/unified/unified_system_tray.h
@@ -9,6 +9,7 @@ #include <memory> #include "ash/ash_export.h" +#include "ash/public/cpp/accelerators.h" #include "ash/public/cpp/shelf_config.h" #include "ash/system/tray/tray_background_view.h" #include "base/time/time.h" @@ -152,6 +153,7 @@ bool ShouldEnableExtraKeyboardAccessibility() override; views::Widget* GetBubbleWidget() const override; const char* GetClassName() const override; + absl::optional<AcceleratorAction> GetAcceleratorAction() const override; // ShelfConfig::Observer: void OnShelfConfigUpdated() override;
diff --git a/ash/test/ash_test_views_delegate.cc b/ash/test/ash_test_views_delegate.cc index 6ae77b5..8c1b798a 100644 --- a/ash/test/ash_test_views_delegate.cc +++ b/ash/test/ash_test_views_delegate.cc
@@ -4,11 +4,20 @@ #include "ash/test/ash_test_views_delegate.h" +#include "ash/accelerators/accelerator_controller_impl.h" #include "ash/shell.h" #include "chromeos/ui/frame/frame_utils.h" namespace ash { +namespace { + +void ProcessAcceleratorNow(const ui::Accelerator& accelerator) { + ash::AcceleratorController::Get()->Process(accelerator); +} + +} // namespace + AshTestViewsDelegate::AshTestViewsDelegate() = default; AshTestViewsDelegate::~AshTestViewsDelegate() = default; @@ -28,10 +37,14 @@ views::TestViewsDelegate::ProcessMenuAcceleratorResult AshTestViewsDelegate::ProcessAcceleratorWhileMenuShowing( const ui::Accelerator& accelerator) { - if (accelerator == close_menu_accelerator_) - return ProcessMenuAcceleratorResult::CLOSE_MENU; + if (ash::AcceleratorController::Get()->OnMenuAccelerator(accelerator)) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(ProcessAcceleratorNow, accelerator)); + return views::ViewsDelegate::ProcessMenuAcceleratorResult::CLOSE_MENU; + } - return ProcessMenuAcceleratorResult::LEAVE_MENU_OPEN; + ProcessAcceleratorNow(accelerator); + return views::ViewsDelegate::ProcessMenuAcceleratorResult::LEAVE_MENU_OPEN; } } // namespace ash
diff --git a/ash/wallpaper/test_wallpaper_controller_client.cc b/ash/wallpaper/test_wallpaper_controller_client.cc index 4bd969c..1d35734 100644 --- a/ash/wallpaper/test_wallpaper_controller_client.cc +++ b/ash/wallpaper/test_wallpaper_controller_client.cc
@@ -4,6 +4,7 @@ #include "ash/wallpaper/test_wallpaper_controller_client.h" +#include "base/logging.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace ash { @@ -19,6 +20,7 @@ fetch_daily_refresh_wallpaper_param_ = std::string(); fetch_daily_refresh_info_fails_ = false; save_wallpaper_to_drive_fs_account_id.clear(); + fake_files_ids_.clear(); } // WallpaperControllerClient: @@ -58,4 +60,15 @@ return true; } +void TestWallpaperControllerClient::GetFilesId( + const AccountId& account_id, + base::OnceCallback<void(const std::string&)> files_id_callback) const { + auto iter = fake_files_ids_.find(account_id); + if (iter == fake_files_ids_.end()) { + LOG(ERROR) << "No fake files id for account id: " << account_id; + return; + } + std::move(files_id_callback).Run(iter->second); +} + } // namespace ash
diff --git a/ash/wallpaper/test_wallpaper_controller_client.h b/ash/wallpaper/test_wallpaper_controller_client.h index d940733a..86bc4bb 100644 --- a/ash/wallpaper/test_wallpaper_controller_client.h +++ b/ash/wallpaper/test_wallpaper_controller_client.h
@@ -7,6 +7,8 @@ #include <stddef.h> +#include <unordered_map> + #include "ash/public/cpp/wallpaper/wallpaper_controller_client.h" #include "components/account_id/account_id.h" @@ -40,6 +42,11 @@ fetch_daily_refresh_info_fails_ = fails; } + void set_fake_files_id_for_account_id(const AccountId& account_id, + std::string fake_files_id) { + fake_files_ids_[account_id] = fake_files_id; + } + void ResetCounts(); // WallpaperControllerClient: @@ -53,6 +60,9 @@ DailyWallpaperUrlFetchedCallback callback) override; bool SaveWallpaperToDriveFs(const AccountId& account_id, const base::FilePath& origin) override; + void GetFilesId(const AccountId& account_id, + base::OnceCallback<void(const std::string&)> + files_id_callback) const override; private: size_t open_count_ = 0; @@ -62,6 +72,7 @@ std::string fetch_daily_refresh_wallpaper_param_; bool fetch_daily_refresh_info_fails_ = false; AccountId save_wallpaper_to_drive_fs_account_id; + std::unordered_map<AccountId, std::string> fake_files_ids_; }; } // namespace ash
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc index 0bd5bd7..8a7aa7c 100644 --- a/ash/wallpaper/wallpaper_controller_impl.cc +++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -998,21 +998,19 @@ void WallpaperControllerImpl::SetCustomWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const base::FilePath& file_path, WallpaperLayout layout, bool preview_mode, SetCustomWallpaperCallback callback) { ReadAndDecodeWallpaper( base::BindOnce(&WallpaperControllerImpl::OnCustomWallpaperDecoded, - weak_factory_.GetWeakPtr(), account_id, wallpaper_files_id, - file_path, layout, preview_mode, std::move(callback)), + weak_factory_.GetWeakPtr(), account_id, file_path, layout, + preview_mode, std::move(callback)), sequenced_task_runner_, file_path); } void WallpaperControllerImpl::SetCustomWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, WallpaperLayout layout, const gfx::ImageSkia& image, @@ -1024,11 +1022,10 @@ const bool is_active_user = IsActiveUser(account_id); if (preview_mode) { DCHECK(is_active_user); - confirm_preview_wallpaper_callback_ = - base::BindOnce(&WallpaperControllerImpl::SaveAndSetWallpaper, - weak_factory_.GetWeakPtr(), account_id, - wallpaper_files_id, file_name, CUSTOMIZED, layout, - /*show_wallpaper=*/false, image); + confirm_preview_wallpaper_callback_ = base::BindOnce( + &WallpaperControllerImpl::SaveAndSetWallpaper, + weak_factory_.GetWeakPtr(), account_id, file_name, CUSTOMIZED, layout, + /*show_wallpaper=*/false, image); reload_preview_wallpaper_callback_ = base::BindRepeating( &WallpaperControllerImpl::ShowWallpaperImage, weak_factory_.GetWeakPtr(), image, @@ -1041,7 +1038,7 @@ reload_preview_wallpaper_callback_.Run(); } else { SaveAndSetWallpaperWithCompletion( - account_id, wallpaper_files_id, file_name, CUSTOMIZED, layout, + account_id, file_name, CUSTOMIZED, layout, /*show_wallpaper=*/is_active_user, image, base::BindOnce(&WallpaperControllerImpl::SaveWallpaperToDriveFs, weak_factory_.GetWeakPtr(), account_id)); @@ -1111,14 +1108,12 @@ DecodeImageData(std::move(decoded_callback), image_data); } -void WallpaperControllerImpl::SetDefaultWallpaper( - const AccountId& account_id, - const std::string& wallpaper_files_id, - bool show_wallpaper) { +void WallpaperControllerImpl::SetDefaultWallpaper(const AccountId& account_id, + bool show_wallpaper) { if (!CanSetUserWallpaper(account_id)) return; - RemoveUserWallpaper(account_id, wallpaper_files_id); + RemoveUserWallpaper(account_id); if (!InitializeUserWallpaperInfo(account_id)) { LOG(ERROR) << "Initializing user wallpaper info fails. This should never " "happen except in tests."; @@ -1145,7 +1140,6 @@ void WallpaperControllerImpl::SetPolicyWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& data) { // There is no visible wallpaper in kiosk mode. if (IsInKioskMode()) @@ -1155,8 +1149,8 @@ const bool show_wallpaper = IsActiveUser(account_id); DecodeImageCallback callback = base::BindOnce( &WallpaperControllerImpl::SaveAndSetWallpaper, weak_factory_.GetWeakPtr(), - account_id, wallpaper_files_id, kPolicyWallpaperFile, POLICY, - WALLPAPER_LAYOUT_CENTER_CROPPED, show_wallpaper); + account_id, kPolicyWallpaperFile, POLICY, WALLPAPER_LAYOUT_CENTER_CROPPED, + show_wallpaper); if (bypass_decode_for_testing_) { std::move(callback).Run(CreateSolidColorWallpaper(kDefaultWallpaperColor)); @@ -1185,7 +1179,6 @@ bool WallpaperControllerImpl::SetThirdPartyWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, WallpaperLayout layout, const gfx::ImageSkia& image) { @@ -1193,8 +1186,8 @@ bool allowed_to_show_wallpaper = IsActiveUser(account_id); if (allowed_to_set_wallpaper) { - SaveAndSetWallpaper(account_id, wallpaper_files_id, file_name, CUSTOMIZED, - layout, allowed_to_show_wallpaper, image); + SaveAndSetWallpaper(account_id, file_name, CUSTOMIZED, layout, + allowed_to_show_wallpaper, image); } return allowed_to_set_wallpaper && allowed_to_show_wallpaper; } @@ -1376,16 +1369,13 @@ ReloadWallpaper(/*clear_cache=*/false); } -void WallpaperControllerImpl::RemoveUserWallpaper( - const AccountId& account_id, - const std::string& wallpaper_files_id) { +void WallpaperControllerImpl::RemoveUserWallpaper(const AccountId& account_id) { RemoveUserWallpaperInfo(account_id); - RemoveUserWallpaperImpl(account_id, wallpaper_files_id); + RemoveUserWallpaperImpl(account_id); } void WallpaperControllerImpl::RemovePolicyWallpaper( - const AccountId& account_id, - const std::string& wallpaper_files_id) { + const AccountId& account_id) { if (!IsPolicyControlled(account_id)) return; @@ -1395,7 +1385,7 @@ // Removes the wallpaper info so that the user is no longer policy controlled, // otherwise setting default wallpaper is not allowed. RemoveUserWallpaperInfo(account_id); - SetDefaultWallpaper(account_id, wallpaper_files_id, show_wallpaper); + SetDefaultWallpaper(account_id, show_wallpaper); } void WallpaperControllerImpl::GetOfflineWallpaperList( @@ -1743,6 +1733,20 @@ } void WallpaperControllerImpl::RemoveUserWallpaperImpl( + const AccountId& account_id) { + if (wallpaper_controller_client_) { + wallpaper_controller_client_->GetFilesId( + account_id, + base::BindOnce( + &WallpaperControllerImpl::RemoveUserWallpaperImplWithFilesId, + weak_factory_.GetWeakPtr(), account_id)); + } else { + LOG(ERROR) << "Failed to remove wallpaper. wallpaper_controller_client_ no " + "longer exists."; + } +} + +void WallpaperControllerImpl::RemoveUserWallpaperImplWithFilesId( const AccountId& account_id, const std::string& wallpaper_files_id) { if (wallpaper_files_id.empty()) @@ -2035,26 +2039,42 @@ void WallpaperControllerImpl::SaveAndSetWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, WallpaperType type, WallpaperLayout layout, bool show_wallpaper, const gfx::ImageSkia& image) { - SaveAndSetWallpaperWithCompletion(account_id, wallpaper_files_id, file_name, - type, layout, show_wallpaper, image, - base::DoNothing()); + SaveAndSetWallpaperWithCompletion(account_id, file_name, type, layout, + show_wallpaper, image, base::DoNothing()); } void WallpaperControllerImpl::SaveAndSetWallpaperWithCompletion( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, WallpaperType type, WallpaperLayout layout, bool show_wallpaper, const gfx::ImageSkia& image, FilePathCallback image_saved_callback) { + if (wallpaper_controller_client_) { + wallpaper_controller_client_->GetFilesId( + account_id, + base::BindOnce( + &WallpaperControllerImpl::SaveAndSetWallpaperWithCompletionFilesId, + weak_factory_.GetWeakPtr(), account_id, file_name, type, layout, + show_wallpaper, image, std::move(image_saved_callback))); + } +} + +void WallpaperControllerImpl::SaveAndSetWallpaperWithCompletionFilesId( + const AccountId& account_id, + const std::string& file_name, + WallpaperType type, + WallpaperLayout layout, + bool show_wallpaper, + const gfx::ImageSkia& image, + FilePathCallback image_saved_callback, + const std::string& wallpaper_files_id) { // If the image of the new wallpaper is empty, the current wallpaper is still // kept instead of reverting to the default. if (image.isNull()) { @@ -2114,7 +2134,6 @@ void WallpaperControllerImpl::OnCustomWallpaperDecoded( const AccountId& account_id, - const std::string& wallpaper_files_id, const base::FilePath& path, WallpaperLayout layout, bool preview_mode, @@ -2122,8 +2141,8 @@ const gfx::ImageSkia& image) { bool success = !image.isNull(); if (success) { - SetCustomWallpaper(account_id, wallpaper_files_id, path.BaseName().value(), - layout, image, preview_mode); + SetCustomWallpaper(account_id, path.BaseName().value(), layout, image, + preview_mode); } std::move(callback).Run(success); } @@ -2394,9 +2413,7 @@ NOTIMPLEMENTED(); break; case DEFAULT: - if (wallpaper_controller_client_) { - wallpaper_controller_client_->SetDefaultWallpaper(account_id, true); - } + SetDefaultWallpaper(account_id, /*show_wallpaper=*/true); break; case DAILY: case ONLINE:
diff --git a/ash/wallpaper/wallpaper_controller_impl.h b/ash/wallpaper/wallpaper_controller_impl.h index 5a0abbd..df4e67c 100644 --- a/ash/wallpaper/wallpaper_controller_impl.h +++ b/ash/wallpaper/wallpaper_controller_impl.h
@@ -236,13 +236,11 @@ const base::FilePath& custom_wallpapers, const base::FilePath& device_policy_wallpaper) override; void SetCustomWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const base::FilePath& file_path, WallpaperLayout layout, bool preview_mode, SetCustomWallpaperCallback callback) override; void SetCustomWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, WallpaperLayout layout, const gfx::ImageSkia& image, @@ -255,18 +253,15 @@ const std::string& image_data, SetOnlineWallpaperCallback callback) override; void SetDefaultWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, bool show_wallpaper) override; void SetCustomizedDefaultWallpaperPaths( const base::FilePath& customized_default_small_path, const base::FilePath& customized_default_large_path) override; void SetPolicyWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& data) override; void SetDevicePolicyWallpaperPath( const base::FilePath& device_policy_wallpaper_path) override; bool SetThirdPartyWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, WallpaperLayout layout, const gfx::ImageSkia& image) override; @@ -279,10 +274,8 @@ void ShowOneShotWallpaper(const gfx::ImageSkia& image) override; void ShowAlwaysOnTopWallpaper(const base::FilePath& image_path) override; void RemoveAlwaysOnTopWallpaper() override; - void RemoveUserWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id) override; - void RemovePolicyWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id) override; + void RemoveUserWallpaper(const AccountId& account_id) override; + void RemovePolicyWallpaper(const AccountId& account_id) override; void GetOfflineWallpaperList( GetOfflineWallpaperListCallback callback) override; void SetAnimationDuration(base::TimeDelta animation_duration) override; @@ -397,8 +390,11 @@ // Implementation of |RemoveUserWallpaper|, which deletes |account_id|'s // custom wallpapers and directories. - void RemoveUserWallpaperImpl(const AccountId& account_id, - const std::string& wallpaper_files_id); + void RemoveUserWallpaperImpl(const AccountId& account_id); + + void RemoveUserWallpaperImplWithFilesId( + const AccountId& account_id, + const std::string& wallpaper_files_id); // Implementation of |SetDefaultWallpaper|. Sets wallpaper to default if // |show_wallpaper| is true. Otherwise just save the defaut wallpaper to @@ -467,7 +463,6 @@ // |show_wallpaper| is true, otherwise only sets the wallpaper info and // updates the cache. void SaveAndSetWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, WallpaperType type, WallpaperLayout layout, @@ -477,7 +472,6 @@ // |image_saved| is only called on success. void SaveAndSetWallpaperWithCompletion( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, WallpaperType type, WallpaperLayout layout, @@ -485,8 +479,17 @@ const gfx::ImageSkia& image, base::OnceCallback<void(const base::FilePath&)> image_saved_callback); + void SaveAndSetWallpaperWithCompletionFilesId( + const AccountId& account_id, + const std::string& file_name, + WallpaperType type, + WallpaperLayout layout, + bool show_wallpaper, + const gfx::ImageSkia& image, + base::OnceCallback<void(const base::FilePath&)> image_saved_callback, + const std::string& wallpaper_files_id); + void OnCustomWallpaperDecoded(const AccountId& account_id, - const std::string& wallpaper_files_id, const base::FilePath& path, WallpaperLayout layout, bool preview_mode,
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc index cdac5e4d..2dd071e 100644 --- a/ash/wallpaper/wallpaper_controller_unittest.cc +++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -388,6 +388,12 @@ base::FilePath policy_wallpaper; controller_->Init(user_data_dir_.GetPath(), online_wallpaper_dir_.GetPath(), custom_wallpaper_dir_.GetPath(), policy_wallpaper); + client_.ResetCounts(); + controller_->SetClient(&client_); + client_.set_fake_files_id_for_account_id(account_id_1, + wallpaper_files_id_1); + client_.set_fake_files_id_for_account_id(account_id_2, + wallpaper_files_id_2); } void TearDown() override { @@ -479,6 +485,7 @@ // can retrieve the images and info. void CreateAndSaveWallpapers(const AccountId& account_id) { std::string wallpaper_files_id = GetDummyFileId(account_id); + std::string file_name = GetDummyFileName(account_id); base::FilePath small_wallpaper_path = GetCustomWallpaperPath(WallpaperControllerImpl::kSmallWallpaperSubDir, @@ -638,10 +645,10 @@ user_manager::FakeUserManager* fake_user_manager_ = nullptr; std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_; + TestWallpaperControllerClient client_; std::unique_ptr<TestImageDownloader> test_image_downloader_; const AccountId kChildAccountId = AccountId::FromUserEmail(kChildEmail); - const std::string kChildWallpaperFilesId = GetDummyFileId(kChildAccountId); private: data_decoder::test::InProcessDataDecoder in_process_data_decoder_; @@ -650,16 +657,13 @@ }; TEST_F(WallpaperControllerTest, Client) { - TestWallpaperControllerClient client; - controller_->SetClient(&client); - base::FilePath empty_path; controller_->Init(empty_path, empty_path, empty_path, empty_path); - EXPECT_EQ(0u, client.open_count()); + EXPECT_EQ(0u, client_.open_count()); EXPECT_TRUE(controller_->CanOpenWallpaperPicker()); controller_->OpenWallpaperPickerIfAllowed(); - EXPECT_EQ(1u, client.open_count()); + EXPECT_EQ(1u, client_.open_count()); } TEST_F(WallpaperControllerTest, BasicReparenting) { @@ -976,16 +980,12 @@ gfx::ImageSkia image = CreateImage(640, 480, kWallpaperColor); WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER; - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(kUser1); // Set a custom wallpaper for |kUser1|. Verify the wallpaper is set // successfully and wallpaper info is updated. ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, image, + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, image, false /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); @@ -997,7 +997,7 @@ base::FilePath(wallpaper_files_id_1).Append(file_name_1).value(), layout, CUSTOMIZED, base::Time::Now().LocalMidnight()); EXPECT_EQ(wallpaper_info, expected_wallpaper_info); - EXPECT_EQ(account_id_1, client.get_save_wallpaper_to_drive_fs_account_id()); + EXPECT_EQ(account_id_1, client_.get_save_wallpaper_to_drive_fs_account_id()); // Now set another custom wallpaper for |kUser1|. Verify that the on-screen // wallpaper doesn't change since |kUser1| is not active, but wallpaper info @@ -1006,8 +1006,7 @@ const SkColor custom_wallpaper_color = SK_ColorCYAN; image = CreateImage(640, 480, custom_wallpaper_color); ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, image, + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, image, false /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(0, GetWallpaperCount()); @@ -1069,9 +1068,9 @@ // subsequent calls will be no-op since we intentionally prevent reloading the // same wallpaper.) ClearWallpaperCount(); - controller_->SetCustomWallpaper( - account_id_1, wallpaper_files_id_1, file_name_1, layout, - CreateImage(640, 480, kWallpaperColor), /*preview_mode=*/false); + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, + CreateImage(640, 480, kWallpaperColor), + /*preview_mode=*/false); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(controller_->GetWallpaperType(), CUSTOMIZED); @@ -1243,8 +1242,7 @@ // Set a policy wallpaper. Verify that the user becomes policy controlled and // the wallpaper info is updated. ClearWallpaperCount(); - controller_->SetPolicyWallpaper(account_id_1, wallpaper_files_id_1, - std::string() /*data=*/); + controller_->SetPolicyWallpaper(account_id_1, std::string() /*data=*/); RunAllTasksUntilIdle(); EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &wallpaper_info)); WallpaperInfo policy_wallpaper_info(base::FilePath(wallpaper_files_id_1) @@ -1276,7 +1274,7 @@ // Remove the policy wallpaper. Verify the wallpaper info is reset to default // and the user is no longer policy controlled. ClearWallpaperCount(); - controller_->RemovePolicyWallpaper(account_id_1, wallpaper_files_id_1); + controller_->RemovePolicyWallpaper(account_id_1); WaitUntilCustomWallpapersDeleted(account_id_1); EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &wallpaper_info)); WallpaperInfo default_wallpaper_info(std::string(), @@ -1301,6 +1299,7 @@ auto verify_custom_wallpaper_info = [&]() { EXPECT_EQ(CUSTOMIZED, controller_->GetWallpaperType()); EXPECT_EQ(kWallpaperColor, GetWallpaperColor()); + WallpaperInfo wallpaper_info; EXPECT_TRUE( controller_->GetUserWallpaperInfo(account_id_1, &wallpaper_info)); @@ -1315,7 +1314,7 @@ SimulateUserLogin(kUser1); ClearWallpaperCount(); controller_->SetCustomWallpaper( - account_id_1, wallpaper_files_id_1, file_name_1, WALLPAPER_LAYOUT_CENTER, + account_id_1, file_name_1, WALLPAPER_LAYOUT_CENTER, CreateImage(640, 480, kWallpaperColor), false /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); @@ -1324,7 +1323,7 @@ // Verify RemovePolicyWallpaper() is a no-op when the user doesn't have a // policy wallpaper. - controller_->RemovePolicyWallpaper(account_id_1, wallpaper_files_id_1); + controller_->RemovePolicyWallpaper(account_id_1); RunAllTasksUntilIdle(); verify_custom_wallpaper_info(); } @@ -1343,8 +1342,7 @@ gfx::ImageSkia third_party_wallpaper = CreateImage(640, 480, kWallpaperColor); ClearWallpaperCount(); EXPECT_TRUE(controller_->SetThirdPartyWallpaper( - account_id_1, wallpaper_files_id_1, file_name_1, layout, - third_party_wallpaper)); + account_id_1, file_name_1, layout, third_party_wallpaper)); // Verify the wallpaper is shown. EXPECT_EQ(1, GetWallpaperCount()); // Verify the user wallpaper info is updated. @@ -1360,22 +1358,20 @@ SimulateUserLogin(kUser2); ClearWallpaperCount(); EXPECT_FALSE(controller_->SetThirdPartyWallpaper( - account_id_1, wallpaper_files_id_2, file_name_2, layout, - third_party_wallpaper)); + account_id_1, file_name_2, layout, third_party_wallpaper)); // Verify the wallpaper is not shown. EXPECT_EQ(0, GetWallpaperCount()); // Verify the wallpaper info for |kUser1| is updated, because setting // wallpaper is still allowed for non-active users. EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &wallpaper_info)); WallpaperInfo expected_wallpaper_info_2( - base::FilePath(wallpaper_files_id_2).Append(file_name_2).value(), layout, + base::FilePath(wallpaper_files_id_1).Append(file_name_2).value(), layout, CUSTOMIZED, base::Time::Now().LocalMidnight()); EXPECT_EQ(wallpaper_info, expected_wallpaper_info_2); // Set a policy wallpaper for |kUser2|. Verify that |kUser2| becomes policy // controlled. - controller_->SetPolicyWallpaper(account_id_2, wallpaper_files_id_2, - std::string() /*data=*/); + controller_->SetPolicyWallpaper(account_id_2, std::string() /*data=*/); RunAllTasksUntilIdle(); EXPECT_TRUE(controller_->IsPolicyControlled(account_id_2)); EXPECT_TRUE(controller_->IsActiveUserWallpaperControlledByPolicy()); @@ -1384,8 +1380,7 @@ // third-party wallpapers cannot be set for policy controlled users. ClearWallpaperCount(); EXPECT_FALSE(controller_->SetThirdPartyWallpaper( - account_id_2, wallpaper_files_id_1, file_name_1, layout, - third_party_wallpaper)); + account_id_2, file_name_1, layout, third_party_wallpaper)); // Verify the wallpaper is not shown. EXPECT_EQ(0, GetWallpaperCount()); // Verify |kUser2| is still policy controlled and has the policy wallpaper @@ -1421,8 +1416,7 @@ RunAllTasksUntilIdle(); ClearWallpaperCount(); ClearDecodeFilePaths(); - controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT); @@ -1442,8 +1436,7 @@ RunAllTasksUntilIdle(); ClearWallpaperCount(); ClearDecodeFilePaths(); - controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT); @@ -1463,8 +1456,7 @@ RunAllTasksUntilIdle(); ClearWallpaperCount(); ClearDecodeFilePaths(); - controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT); @@ -1488,8 +1480,7 @@ RunAllTasksUntilIdle(); ClearWallpaperCount(); ClearDecodeFilePaths(); - controller_->SetDefaultWallpaper(kChildAccountId, kChildWallpaperFilesId, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(kChildAccountId, true /*show_wallpaper=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT); @@ -1503,8 +1494,7 @@ RunAllTasksUntilIdle(); ClearWallpaperCount(); ClearDecodeFilePaths(); - controller_->SetDefaultWallpaper(kChildAccountId, kChildWallpaperFilesId, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(kChildAccountId, true /*show_wallpaper=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT); @@ -1536,8 +1526,7 @@ const AccountId guest_id = AccountId::FromUserEmail(user_manager::kGuestUserName); fake_user_manager_->AddGuestUser(guest_id); - controller_->SetDefaultWallpaper(guest_id, wallpaper_files_id_1, - /*show_wallpaper=*/true); + controller_->SetDefaultWallpaper(guest_id, /*show_wallpaper=*/true); WallpaperInfo wallpaper_info; WallpaperInfo default_wallpaper_info(std::string(), @@ -1556,12 +1545,11 @@ // Second, set a user policy for which is being set for another // user and verifying that the policy has been applied successfully. WallpaperInfo policy_wallpaper_info; - controller_->SetPolicyWallpaper(account_id_1, wallpaper_files_id_2, - /*data=*/std::string()); + controller_->SetPolicyWallpaper(account_id_1, /*data=*/std::string()); EXPECT_TRUE( controller_->GetUserWallpaperInfo(account_id_1, &policy_wallpaper_info)); WallpaperInfo expected_policy_wallpaper_info( - base::FilePath(wallpaper_files_id_2) + base::FilePath(wallpaper_files_id_1) .Append("policy-controlled.jpeg") .value(), WALLPAPER_LAYOUT_CENTER_CROPPED, POLICY, @@ -1605,8 +1593,7 @@ RunAllTasksUntilIdle(); ClearWallpaperCount(); ClearDecodeFilePaths(); - controller_->SetDefaultWallpaper(guest_id, wallpaper_files_id_1, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(guest_id, true /*show_wallpaper=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT); @@ -1620,8 +1607,7 @@ RunAllTasksUntilIdle(); ClearWallpaperCount(); ClearDecodeFilePaths(); - controller_->SetDefaultWallpaper(guest_id, wallpaper_files_id_1, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(guest_id, true /*show_wallpaper=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT); @@ -1637,8 +1623,8 @@ // Verify that |SetCustomWallpaper| doesn't set wallpaper in kiosk mode, and // |account_id_1|'s wallpaper info is not updated. ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, WALLPAPER_LAYOUT_CENTER, image, + controller_->SetCustomWallpaper(account_id_1, file_name_1, + WALLPAPER_LAYOUT_CENTER, image, false /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(0, GetWallpaperCount()); @@ -1668,8 +1654,7 @@ // Verify that |SetDefaultWallpaper| doesn't set wallpaper in kiosk mode, and // |account_id_1|'s wallpaper info is not updated. ClearWallpaperCount(); - controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/); RunAllTasksUntilIdle(); EXPECT_EQ(0, GetWallpaperCount()); EXPECT_FALSE( @@ -1688,16 +1673,15 @@ SimulateUserLogin(kUser1); // Set a policy wallpaper for the user. Verify the user is policy controlled. - controller_->SetPolicyWallpaper(account_id_1, wallpaper_files_id_1, - std::string() /*data=*/); + controller_->SetPolicyWallpaper(account_id_1, std::string() /*data=*/); RunAllTasksUntilIdle(); EXPECT_TRUE(controller_->IsPolicyControlled(account_id_1)); // Verify that |SetCustomWallpaper| doesn't set wallpaper when policy is // enforced, and the user wallpaper info is not updated. ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, WALLPAPER_LAYOUT_CENTER, image, + controller_->SetCustomWallpaper(account_id_1, file_name_1, + WALLPAPER_LAYOUT_CENTER, image, false /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(0, GetWallpaperCount()); @@ -1732,8 +1716,7 @@ // Verify that |SetDefaultWallpaper| doesn't set wallpaper when policy is // enforced, and the user wallpaper info is not updated. ClearWallpaperCount(); - controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/); RunAllTasksUntilIdle(); EXPECT_EQ(0, GetWallpaperCount()); EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &wallpaper_info)); @@ -1774,15 +1757,14 @@ EXPECT_TRUE(controller_->GetPathFromCache(account_id_1, &path)); // Verify |SetDefaultWallpaper| clears wallpaper cache. - controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/); EXPECT_FALSE( controller_->GetWallpaperFromCache(account_id_1, &cached_wallpaper)); EXPECT_FALSE(controller_->GetPathFromCache(account_id_1, &path)); // Verify |SetCustomWallpaper| updates wallpaper cache for |user1|. - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, WALLPAPER_LAYOUT_CENTER, image, + controller_->SetCustomWallpaper(account_id_1, file_name_1, + WALLPAPER_LAYOUT_CENTER, image, false /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_TRUE( @@ -1790,7 +1772,7 @@ EXPECT_TRUE(controller_->GetPathFromCache(account_id_1, &path)); // Verify |RemoveUserWallpaper| clears wallpaper cache. - controller_->RemoveUserWallpaper(account_id_1, wallpaper_files_id_1); + controller_->RemoveUserWallpaper(account_id_1); EXPECT_FALSE( controller_->GetWallpaperFromCache(account_id_1, &cached_wallpaper)); EXPECT_FALSE(controller_->GetPathFromCache(account_id_1, &path)); @@ -1921,7 +1903,7 @@ WindowState::Get(wallpaper_picker_window.get())->Activate(); ClearWallpaperCount(); controller_->SetCustomWallpaper( - account_id_1, wallpaper_files_id_1, file_name_1, WALLPAPER_LAYOUT_CENTER, + account_id_1, file_name_1, WALLPAPER_LAYOUT_CENTER, CreateImage(640, 480, kWallpaperColor), true /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); @@ -1962,8 +1944,7 @@ // Set a custom wallpaper for the user. Verify that it's set successfully // and the wallpaper info is updated. ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, image, + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, image, false /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); @@ -2026,6 +2007,7 @@ base::FilePath small_wallpaper_path_1 = GetCustomWallpaperPath(WallpaperControllerImpl::kSmallWallpaperSubDir, wallpaper_files_id_1, file_name_1); + // Set a custom wallpaper for |kUser1| and verify the wallpaper exists. CreateAndSaveWallpapers(account_id_1); EXPECT_TRUE(base::PathExists(small_wallpaper_path_1)); @@ -2039,7 +2021,7 @@ EXPECT_TRUE(base::PathExists(small_wallpaper_path_2)); // Simulate the removal of |kUser2|. - controller_->RemoveUserWallpaper(account_id_2, wallpaper_files_id_2); + controller_->RemoveUserWallpaper(account_id_2); // Wait until all files under the user's custom wallpaper directory are // removed. WaitUntilCustomWallpapersDeleted(account_id_2); @@ -2062,11 +2044,10 @@ // Now login another user and set a default wallpaper for the user. SimulateUserLogin(kUser2); - controller_->SetDefaultWallpaper(account_id_2, wallpaper_files_id_2, - true /*show_wallpaper=*/); + controller_->SetDefaultWallpaper(account_id_2, true /*show_wallpaper=*/); // Simulate the removal of |kUser2|. - controller_->RemoveUserWallpaper(account_id_2, wallpaper_files_id_2); + controller_->RemoveUserWallpaper(account_id_2); // Verify that the other user's wallpaper is not affected. EXPECT_TRUE(base::PathExists(small_wallpaper_path_1)); @@ -2083,8 +2064,7 @@ EXPECT_FALSE(controller_->IsActiveUserWallpaperControlledByPolicy()); // Set a policy wallpaper for the active user. Verify that the active user // becomes policy controlled. - controller_->SetPolicyWallpaper(account_id_1, wallpaper_files_id_1, - std::string() /*data=*/); + controller_->SetPolicyWallpaper(account_id_1, std::string() /*data=*/); RunAllTasksUntilIdle(); EXPECT_TRUE(controller_->IsActiveUserWallpaperControlledByPolicy()); @@ -2326,9 +2306,6 @@ } TEST_F(WallpaperControllerTest, ClosePreviewWallpaperOnOverviewStart) { - TestWallpaperControllerClient client; - controller_->SetClient(&client); - // Verify the user starts with a default wallpaper and the user wallpaper info // is initialized with default values. SimulateUserLogin(kUser1); @@ -2355,9 +2332,8 @@ gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor); EXPECT_NE(kWallpaperColor, GetWallpaperColor()); ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, custom_wallpaper, - true /*preview_mode=*/); + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, + custom_wallpaper, true /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(kWallpaperColor, GetWallpaperColor()); @@ -2377,13 +2353,10 @@ EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT); EXPECT_EQ(user_wallpaper_info, default_wallpaper_info); EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); - EXPECT_EQ(1u, client.close_preview_count()); + EXPECT_EQ(1u, client_.close_preview_count()); } TEST_F(WallpaperControllerTest, ClosePreviewWallpaperOnWindowCycleStart) { - TestWallpaperControllerClient client; - controller_->SetClient(&client); - // Verify the user starts with a default wallpaper and the user wallpaper info // is initialized with default values. SimulateUserLogin(kUser1); @@ -2410,9 +2383,8 @@ gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor); EXPECT_NE(kWallpaperColor, GetWallpaperColor()); ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, custom_wallpaper, - true /*preview_mode=*/); + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, + custom_wallpaper, true /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(kWallpaperColor, GetWallpaperColor()); @@ -2432,7 +2404,7 @@ EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT); EXPECT_EQ(user_wallpaper_info, default_wallpaper_info); EXPECT_TRUE(Shell::Get()->window_cycle_controller()->IsCycling()); - EXPECT_EQ(1u, client.close_preview_count()); + EXPECT_EQ(1u, client_.close_preview_count()); } TEST_F(WallpaperControllerTest, ConfirmPreviewWallpaper) { @@ -2462,9 +2434,8 @@ gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor); EXPECT_NE(kWallpaperColor, GetWallpaperColor()); ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, custom_wallpaper, - true /*preview_mode=*/); + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, + custom_wallpaper, true /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(kWallpaperColor, GetWallpaperColor()); @@ -2481,6 +2452,7 @@ RunAllTasksUntilIdle(); EXPECT_EQ(0, GetWallpaperCount()); EXPECT_EQ(kWallpaperColor, GetWallpaperColor()); + // Verify that the user wallpaper info is now updated to the custom wallpaper // info. WallpaperInfo custom_wallpaper_info( @@ -2571,9 +2543,8 @@ gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor); EXPECT_NE(kWallpaperColor, GetWallpaperColor()); ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, custom_wallpaper, - true /*preview_mode=*/); + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, + custom_wallpaper, true /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(kWallpaperColor, GetWallpaperColor()); @@ -2650,9 +2621,8 @@ gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor); EXPECT_NE(kWallpaperColor, GetWallpaperColor()); ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, custom_wallpaper, - true /*preview_mode=*/); + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, + custom_wallpaper, true /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(kWallpaperColor, GetWallpaperColor()); @@ -2669,15 +2639,15 @@ gfx::ImageSkia synced_custom_wallpaper = CreateImage(640, 480, synced_custom_wallpaper_color); ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_2, - file_name_2, layout, synced_custom_wallpaper, + controller_->SetCustomWallpaper(account_id_1, file_name_2, layout, + synced_custom_wallpaper, false /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(0, GetWallpaperCount()); EXPECT_EQ(kWallpaperColor, GetWallpaperColor()); // However, the user wallpaper info should already be updated to the new info. WallpaperInfo synced_custom_wallpaper_info( - base::FilePath(wallpaper_files_id_2).Append(file_name_2).value(), layout, + base::FilePath(wallpaper_files_id_1).Append(file_name_2).value(), layout, CUSTOMIZED, base::Time::Now().LocalMidnight()); EXPECT_TRUE( controller_->GetUserWallpaperInfo(account_id_1, &user_wallpaper_info)); @@ -2812,13 +2782,13 @@ TEST_F(WallpaperControllerTest, ShowOneShotWallpaper) { gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor); WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER; + SimulateUserLogin(kUser1); // First, set a custom wallpaper for |kUser1|. Verify the wallpaper is shown // successfully and the user wallpaper info is updated. ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, custom_wallpaper, - false /*preview_mode=*/); + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, + custom_wallpaper, false /*preview_mode=*/); RunAllTasksUntilIdle(); EXPECT_EQ(1, GetWallpaperCount()); EXPECT_EQ(kWallpaperColor, GetWallpaperColor()); @@ -2910,8 +2880,8 @@ EXPECT_FALSE(controller_->GetPathFromCache(account_id_1, &path)); ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, WALLPAPER_LAYOUT_CENTER, + controller_->SetCustomWallpaper(account_id_1, file_name_1, + WALLPAPER_LAYOUT_CENTER, CreateImage(640, 480, kWallpaperColor), /*preview_mode=*/false); RunAllTasksUntilIdle(); @@ -3097,9 +3067,6 @@ base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - WallpaperInfo expected_info = InfoWithType(CUSTOMIZED); PutWallpaperInfoInPrefs(account_id_1, expected_info, GetLocalPrefService(), prefs::kUserWallpaperInfo); @@ -3114,9 +3081,6 @@ base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SetBypassDecode(); PutWallpaperInfoInPrefs(account_id_1, InfoWithType(ONLINE), @@ -3137,9 +3101,6 @@ base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(account_id_1.GetUserEmail()); PutWallpaperInfoInPrefs(account_id_1, InfoWithType(DEFAULT), GetProfilePrefService(account_id_1), @@ -3148,34 +3109,32 @@ PutWallpaperInfoInPrefs(account_id_1, InfoWithType(THIRDPARTY), GetLocalPrefService(), prefs::kUserWallpaperInfo); - client.ResetCounts(); + client_.ResetCounts(); controller_->OnActiveUserPrefServiceChanged( GetProfilePrefService(account_id_1)); - EXPECT_EQ(client.set_default_wallpaper_count(), 1u); + WallpaperInfo actual_info; + EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &actual_info)); + EXPECT_EQ(DEFAULT, actual_info.type); } TEST_F(WallpaperControllerTest, HandleWallpaperInfoSyncedDefault) { base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(kUser1); - EXPECT_EQ(client.set_default_wallpaper_count(), 0u); PutWallpaperInfoInPrefs(account_id_1, InfoWithType(DEFAULT), GetProfilePrefService(account_id_1), prefs::kSyncableWallpaperInfo); - EXPECT_EQ(client.set_default_wallpaper_count(), 1u); + WallpaperInfo actual_info; + EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &actual_info)); + EXPECT_EQ(DEFAULT, actual_info.type); } TEST_F(WallpaperControllerTest, HandleWallpaperInfoSyncedLocalIsPolicy) { base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); PutWallpaperInfoInPrefs(account_id_1, InfoWithType(POLICY), GetLocalPrefService(), prefs::kUserWallpaperInfo); @@ -3183,15 +3142,15 @@ PutWallpaperInfoInPrefs(account_id_1, InfoWithType(DEFAULT), GetProfilePrefService(account_id_1), prefs::kSyncableWallpaperInfo); - EXPECT_EQ(client.set_default_wallpaper_count(), 0u); + WallpaperInfo actual_info; + EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &actual_info)); + EXPECT_NE(DEFAULT, actual_info.type); } TEST_F(WallpaperControllerTest, HandleWallpaperInfoSyncedLocalIsThirdParty) { base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); PutWallpaperInfoInPrefs(account_id_1, InfoWithType(THIRDPARTY), GetLocalPrefService(), prefs::kUserWallpaperInfo); @@ -3199,16 +3158,15 @@ PutWallpaperInfoInPrefs(account_id_1, InfoWithType(DEFAULT), GetProfilePrefService(account_id_1), prefs::kSyncableWallpaperInfo); - EXPECT_EQ(client.set_default_wallpaper_count(), 1u); + WallpaperInfo actual_info; + EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &actual_info)); + EXPECT_EQ(DEFAULT, actual_info.type); } TEST_F(WallpaperControllerTest, HandleWallpaperInfoSyncedOnline) { base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SetBypassDecode(); SimulateUserLogin(kUser1); @@ -3228,10 +3186,9 @@ // subsequent calls will be no-op since we intentionally prevent reloading the // same wallpaper.) ClearWallpaperCount(); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, WALLPAPER_LAYOUT_CENTER_CROPPED, - CreateImage(640, 480, kWallpaperColor), - false /*preview_mode=*/); + controller_->SetCustomWallpaper( + account_id_1, file_name_1, WALLPAPER_LAYOUT_CENTER_CROPPED, + CreateImage(640, 480, kWallpaperColor), false /*preview_mode=*/); RunAllTasksUntilIdle(); // Attempt to set an online wallpaper without providing the image data. Verify @@ -3253,11 +3210,8 @@ base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(account_id_1.GetUserEmail()); - EXPECT_EQ(client.migrate_collection_id_from_chrome_app_count(), 1u); + EXPECT_EQ(client_.migrate_collection_id_from_chrome_app_count(), 1u); } TEST_F( @@ -3266,26 +3220,20 @@ base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(account_id_1.GetUserEmail()); controller_->SetDailyRefreshCollectionId("fun_collection"); - EXPECT_EQ(client.migrate_collection_id_from_chrome_app_count(), 1u); + EXPECT_EQ(client_.migrate_collection_id_from_chrome_app_count(), 1u); ClearLogin(); SimulateUserLogin(account_id_1.GetUserEmail()); - EXPECT_EQ(client.migrate_collection_id_from_chrome_app_count(), 1u); + EXPECT_EQ(client_.migrate_collection_id_from_chrome_app_count(), 1u); } TEST_F(WallpaperControllerTest, SetDailyRefreshCollectionId) { base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - std::string expected{"fun_collection"}; SimulateUserLogin(kUser1); controller_->SetDailyRefreshCollectionId(expected); @@ -3298,9 +3246,6 @@ base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(kUser1); controller_->SetDailyRefreshCollectionId(std::string()); PrefService* pref_service = GetProfilePrefService(account_id_1); @@ -3312,9 +3257,6 @@ base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - std::string expected{"fun_collection"}; SimulateUserLogin(kUser1); @@ -3324,16 +3266,13 @@ DayBeforeYesterdayish())); controller_->UpdateDailyRefreshWallpaperForTesting(); - EXPECT_EQ(expected, client.get_fetch_daily_refresh_wallpaper_param()); + EXPECT_EQ(expected, client_.get_fetch_daily_refresh_wallpaper_param()); } TEST_F(WallpaperControllerTest, UpdateDailyRefreshWallpaperCalledOnLogin) { base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - std::string expected{"fun_collection"}; SimulateUserLogin(kUser1); @@ -3349,23 +3288,20 @@ // within the task that it is called in. RunAllTasksUntilIdle(); - EXPECT_EQ(expected, client.get_fetch_daily_refresh_wallpaper_param()); + EXPECT_EQ(expected, client_.get_fetch_daily_refresh_wallpaper_param()); } TEST_F(WallpaperControllerTest, UpdateDailyRefreshWallpaper_NotEnabled) { base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(kUser1); controller_->SetUserWallpaperInfo( account_id_1, WallpaperInfo(std::string(), WALLPAPER_LAYOUT_CENTER, DAILY, DayBeforeYesterdayish())); controller_->UpdateDailyRefreshWallpaperForTesting(); - EXPECT_EQ(std::string(), client.get_fetch_daily_refresh_wallpaper_param()); + EXPECT_EQ(std::string(), client_.get_fetch_daily_refresh_wallpaper_param()); } TEST_F(WallpaperControllerTest, @@ -3373,9 +3309,6 @@ base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - using base::Time; using base::TimeDelta; @@ -3409,9 +3342,7 @@ base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - client.set_fetch_daily_refresh_info_fails(true); + client_.set_fetch_daily_refresh_info_fails(true); SimulateUserLogin(kUser1); controller_->SetDailyRefreshCollectionId("fun_collection"); @@ -3438,9 +3369,6 @@ base::test::ScopedFeatureList scoped_features; scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(kUser1); controller_->SetDailyRefreshCollectionId("fun_collection"); controller_->SetUserWallpaperInfo( @@ -3467,12 +3395,9 @@ gfx::ImageSkia image = CreateImage(640, 480, kWallpaperColor); WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER; - TestWallpaperControllerClient client; - controller_->SetClient(&client); SimulateUserLogin(kUser1); - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, image, + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, image, false /*preview_mode=*/); RunAllTasksUntilIdle(); ClearLogin(); @@ -3481,7 +3406,7 @@ scoped_features.InitAndEnableFeature(features::kWallpaperWebUI); SimulateUserLogin(account_id_1.GetUserEmail()); - EXPECT_EQ(account_id_1, client.get_save_wallpaper_to_drive_fs_account_id()); + EXPECT_EQ(account_id_1, client_.get_save_wallpaper_to_drive_fs_account_id()); } TEST_F(WallpaperControllerTest, OnGoogleDriveMounted) { @@ -3492,12 +3417,9 @@ PutWallpaperInfoInPrefs(account_id_1, local_info, GetLocalPrefService(), prefs::kUserWallpaperInfo); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(kUser1); controller_->OnGoogleDriveMounted(); - EXPECT_EQ(account_id_1, client.get_save_wallpaper_to_drive_fs_account_id()); + EXPECT_EQ(account_id_1, client_.get_save_wallpaper_to_drive_fs_account_id()); } TEST_F(WallpaperControllerTest, OnGoogleDriveMounted_WallpaperIsntCustom) { @@ -3508,11 +3430,8 @@ PutWallpaperInfoInPrefs(account_id_1, local_info, GetLocalPrefService(), prefs::kUserWallpaperInfo); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - controller_->OnGoogleDriveMounted(); - EXPECT_TRUE(client.get_save_wallpaper_to_drive_fs_account_id().empty()); + EXPECT_TRUE(client_.get_save_wallpaper_to_drive_fs_account_id().empty()); } TEST_F(WallpaperControllerTest, OnGoogleDriveMounted_AlreadySynced) { @@ -3523,24 +3442,20 @@ PutWallpaperInfoInPrefs(account_id_1, local_info, GetLocalPrefService(), prefs::kUserWallpaperInfo); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(kUser1); gfx::ImageSkia image = CreateImage(640, 480, kWallpaperColor); WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER; - controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1, - file_name_1, layout, image, + controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, image, false /*preview_mode=*/); RunAllTasksUntilIdle(); - client.ResetCounts(); + client_.ResetCounts(); // Should not reupload image if it has already been synced. controller_->OnGoogleDriveMounted(); - EXPECT_FALSE(client.get_save_wallpaper_to_drive_fs_account_id().is_valid()); + EXPECT_FALSE(client_.get_save_wallpaper_to_drive_fs_account_id().is_valid()); } TEST_F(WallpaperControllerTest, OnGoogleDriveMounted_OldLocalInfo) { @@ -3560,13 +3475,10 @@ GetProfilePrefService(account_id_1), prefs::kSyncableWallpaperInfo); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(kUser1); controller_->OnGoogleDriveMounted(); - EXPECT_FALSE(client.get_save_wallpaper_to_drive_fs_account_id().is_valid()); + EXPECT_FALSE(client_.get_save_wallpaper_to_drive_fs_account_id().is_valid()); } TEST_F(WallpaperControllerTest, OnGoogleDriveMounted_NewLocalInfo) { @@ -3586,13 +3498,10 @@ GetProfilePrefService(account_id_1), prefs::kSyncableWallpaperInfo); - TestWallpaperControllerClient client; - controller_->SetClient(&client); - SimulateUserLogin(kUser1); controller_->OnGoogleDriveMounted(); - EXPECT_EQ(account_id_1, client.get_save_wallpaper_to_drive_fs_account_id()); + EXPECT_EQ(account_id_1, client_.get_save_wallpaper_to_drive_fs_account_id()); } } // namespace ash
diff --git a/ash/wm/window_positioning_utils.cc b/ash/wm/window_positioning_utils.cc index 42646f1..4e6fa19 100644 --- a/ash/wm/window_positioning_utils.cc +++ b/ash/wm/window_positioning_utils.cc
@@ -150,8 +150,8 @@ } // Compute size of the side of the window bound that should be proportional - // |WindowState::snapped_width_ratio_| to that of the work area, i.e. width - // for horizontal layout and height for vertical layout. + // |WindowState::snap_ratio_| to that of the work area, i.e. width for + // horizontal layout and height for vertical layout. gfx::Rect snap_bounds = gfx::Rect(work_area); const int work_area_axis_length = is_horizontal ? work_area.width() : work_area.height();
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc index 5a1314a..544a7e54 100644 --- a/ash/wm/window_state.cc +++ b/ash/wm/window_state.cc
@@ -8,6 +8,7 @@ #include <utility> #include "ash/constants/ash_constants.h" +#include "ash/constants/ash_features.h" #include "ash/focus_cycler.h" #include "ash/metrics/pip_uma.h" #include "ash/public/cpp/app_types_util.h" @@ -149,9 +150,14 @@ return WM_EVENT_NORMAL; } -float GetCurrentSnappedWidthRatio(aura::Window* window) { +float GetCurrentSnapRatio(aura::Window* window) { gfx::Rect maximized_bounds = screen_util::GetMaximizedWindowBoundsInParent(window); + if (features::IsVerticalSplitScreenEnabled() && + !SplitViewController::IsLayoutHorizontal()) { + return static_cast<float>(window->GetTargetBounds().height()) / + static_cast<float>(maximized_bounds.height()); + } return static_cast<float>(window->GetTargetBounds().width()) / static_cast<float>(maximized_bounds.width()); } @@ -407,7 +413,7 @@ void WindowState::OnWMEvent(const WMEvent* event) { current_state_->OnWMEvent(this, event); - UpdateSnappedWidthRatio(event); + UpdateSnapRatio(event); PersistentDesksBarController* bar_controller = Shell::Get()->persistent_desks_bar_controller(); @@ -514,28 +520,26 @@ return old_object; } -void WindowState::UpdateSnappedWidthRatio(const WMEvent* event) { +void WindowState::UpdateSnapRatio(const WMEvent* event) { if (!IsSnapped()) { - snapped_width_ratio_.reset(); + snap_ratio_.reset(); return; } const WMEventType type = event->type(); - // Initializes |snapped_width_ratio_| whenever |event| is snapping event. + // Initializes |snap_ratio_| whenever |event| is snapping event. if (type == WM_EVENT_SNAP_PRIMARY || type == WM_EVENT_SNAP_SECONDARY || type == WM_EVENT_CYCLE_SNAP_PRIMARY || type == WM_EVENT_CYCLE_SNAP_SECONDARY) { - // Since |UpdateSnappedWidthRatio()| is called post WMEvent taking effect, + // Since |UpdateSnapRatio()| is called post WMEvent taking effect, // |window_|'s bounds is in a correct state for ratio update. - snapped_width_ratio_ = - absl::make_optional(GetCurrentSnappedWidthRatio(window_)); + snap_ratio_ = absl::make_optional(GetCurrentSnapRatio(window_)); return; } - // |snapped_width_ratio_| under snapped state may change due to bounds event. + // |snap_ratio_| under snapped state may change due to bounds event. if (event->IsBoundsEvent()) { - snapped_width_ratio_ = - absl::make_optional(GetCurrentSnappedWidthRatio(window_)); + snap_ratio_ = absl::make_optional(GetCurrentSnapRatio(window_)); } } @@ -685,22 +689,44 @@ } void WindowState::AdjustSnappedBounds(gfx::Rect* bounds) { - if (is_dragged() || !IsSnapped()) + auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller(); + const bool in_tablet = + tablet_mode_controller && tablet_mode_controller->InTabletMode(); + + // Tablet mode should use bounds calculation in SplitViewController. + // However, transient state from transitioning clamshell to tablet mode + // might end up calling this function during work area changes, so we avoid + // unnecessary task in that case when it will be overwritten by tablet mode + // work. + if (is_dragged() || !IsSnapped() || in_tablet) return; gfx::Rect maximized_bounds = screen_util::GetMaximizedWindowBoundsInParent(window_); - // TODO(crbug.com/1233342): Update snap width ratio to snap ratio to support - // vertical split. - if (snapped_width_ratio_) { - bounds->set_width( - static_cast<int>(*snapped_width_ratio_ * maximized_bounds.width())); - } - if (GetStateType() == WindowStateType::kPrimarySnapped) - bounds->set_x(maximized_bounds.x()); - else if (GetStateType() == WindowStateType::kSecondarySnapped) - bounds->set_x(maximized_bounds.right() - bounds->width()); - bounds->set_y(maximized_bounds.y()); - bounds->set_height(maximized_bounds.height()); + + const display::Display display = + display::Screen::GetScreen()->GetDisplayNearestWindow(window_); + + // For snapped window, `GetSnappedWindowBounds` computes bounds position + // from snap type and size from |snap_ratio|. + gfx::Rect snapped_bounds = + snap_ratio_ ? GetSnappedWindowBounds( + maximized_bounds, window_, + GetStateType() == WindowStateType::kPrimarySnapped + ? ash::SnapViewType::kPrimary + : ash::SnapViewType::kSecondary, + *snap_ratio_) + : maximized_bounds; + bounds->set_origin(snapped_bounds.origin()); + + // If |snap_ratio_| exists adjust the size of the window. Otherwise only + // maximize it vertically for horizontal screen and maximize horizontally for + // vertical screen. + if (snap_ratio_) + bounds->set_size(snapped_bounds.size()); + else if (display.size().width() > display.size().height()) + bounds->set_height(snapped_bounds.height()); + else + bounds->set_width(snapped_bounds.width()); } void WindowState::UpdateWindowPropertiesFromStateType() {
diff --git a/ash/wm/window_state.h b/ash/wm/window_state.h index dcb780d..5d95eaa 100644 --- a/ash/wm/window_state.h +++ b/ash/wm/window_state.h
@@ -213,11 +213,9 @@ // by this object and the returned object will be owned by the caller. std::unique_ptr<State> SetStateObject(std::unique_ptr<State> new_state); - // Updates |snapped_width_ratio_| based on |event|. - void UpdateSnappedWidthRatio(const WMEvent* event); - absl::optional<float> snapped_width_ratio() const { - return snapped_width_ratio_; - } + // Updates |snap_ratio_| based on |event|. + void UpdateSnapRatio(const WMEvent* event); + absl::optional<float> snap_ratio() const { return snap_ratio_; } // True if the window should be unminimized to the restore bounds, as // opposed to the window's current bounds. |unminimized_to_restore_bounds_| is @@ -410,8 +408,8 @@ void SetBoundsInScreen(const gfx::Rect& bounds_in_screen); // Adjusts the |bounds| so that they are flush with the edge of the - // workspace if the window represented by |window_state| is side snapped. It - // is called for workspace events. + // workspace in clamshell mode if the window represented by |window_state| + // is side snapped. It is called for workspace events. void AdjustSnappedBounds(gfx::Rect* bounds); // Updates the window properties(show state, pin type) according to the @@ -485,10 +483,11 @@ ui::ZOrderLevel cached_z_order_; bool allow_set_bounds_direct_ = false; - // A property to save the ratio between snapped window width and display - // workarea width. It is used to update snapped window width on - // AdjustSnappedBounds() when handling workspace events. - absl::optional<float> snapped_width_ratio_; + // A property to save the ratio between snapped window width (or height + // for vertical layout) and display workarea width (or height). It is used + // to update snapped window width (or height) on AdjustSnappedBounds() when + // handling workspace events. + absl::optional<float> snap_ratio_; // A property to remember the window position which was set before the // auto window position manager changed the window bounds, so that it can
diff --git a/ash/wm/window_state_unittest.cc b/ash/wm/window_state_unittest.cc index eaf6ebd..281b0bd7 100644 --- a/ash/wm/window_state_unittest.cc +++ b/ash/wm/window_state_unittest.cc
@@ -352,7 +352,7 @@ EXPECT_EQ(work_area_size.ToString(), window->bounds().size().ToString()); } -// Tests UpdateSnappedWidthRatio. (1) It should have ratio reset when window +// Tests UpdateSnapRatio. (1) It should have ratio reset when window // enters snapped state; (2) it should update ratio on bounds event when // snapped. TEST_F(WindowStateTest, UpdateSnapWidthRatioTest) { @@ -371,7 +371,7 @@ gfx::Rect(kWorkAreaBounds.x(), kWorkAreaBounds.y(), kWorkAreaBounds.width() / 2, kWorkAreaBounds.height()); EXPECT_EQ(expected, window->GetBoundsInScreen()); - EXPECT_EQ(0.5f, *window_state->snapped_width_ratio()); + EXPECT_EQ(0.5f, *window_state->snap_ratio()); // Drag to change snapped window width. const int kIncreasedWidth = 225; @@ -384,18 +384,18 @@ expected.set_width(expected.width() + kIncreasedWidth); EXPECT_EQ(expected, window->GetBoundsInScreen()); EXPECT_EQ(WindowStateType::kPrimarySnapped, window_state->GetStateType()); - EXPECT_EQ(0.75f, *window_state->snapped_width_ratio()); + EXPECT_EQ(0.75f, *window_state->snap_ratio()); // Another cycle snap left event will restore window state to normal. window_state->OnWMEvent(&cycle_snap_left); EXPECT_EQ(WindowStateType::kNormal, window_state->GetStateType()); - EXPECT_FALSE(window_state->snapped_width_ratio()); + EXPECT_FALSE(window_state->snap_ratio()); // Another cycle snap left event will snap window and reset snapped width // ratio. window_state->OnWMEvent(&cycle_snap_left); EXPECT_EQ(WindowStateType::kPrimarySnapped, window_state->GetStateType()); - EXPECT_EQ(0.5f, *window_state->snapped_width_ratio()); + EXPECT_EQ(0.5f, *window_state->snap_ratio()); } // Tests that dragging and snapping the snapped window update the width ratio @@ -429,7 +429,7 @@ window->layer()->GetAnimator()->Step(base::TimeTicks::Now() + base::TimeDelta::FromSeconds(1)); EXPECT_EQ(expected, window->GetBoundsInScreen()); - EXPECT_EQ(0.5f, *window_state->snapped_width_ratio()); + EXPECT_EQ(0.5f, *window_state->snap_ratio()); // Drag the window to unsnap but do not release. ui::test::EventGenerator* generator = GetEventGenerator(); @@ -438,7 +438,7 @@ generator->MoveMouseBy(5, 0); // While dragged, the window size should restore to its normal bound. EXPECT_EQ(window_normal_size, window->bounds().size()); - EXPECT_EQ(1.0f, *window_state->snapped_width_ratio()); + EXPECT_EQ(1.0f, *window_state->snap_ratio()); // Continue dragging the window and snap it back to the same position. generator->MoveMouseBy(-405, 0); @@ -446,7 +446,7 @@ // The snapped ratio should be correct regardless of whether the animation // is finished or not. - EXPECT_EQ(0.5f, *window_state->snapped_width_ratio()); + EXPECT_EQ(0.5f, *window_state->snap_ratio()); } // Test that snapping left/right preserves the restore bounds.
diff --git a/ash/wm/workspace/multi_window_resize_controller_unittest.cc b/ash/wm/workspace/multi_window_resize_controller_unittest.cc index 3c7323bd..eb72f3d 100644 --- a/ash/wm/workspace/multi_window_resize_controller_unittest.cc +++ b/ash/wm/workspace/multi_window_resize_controller_unittest.cc
@@ -609,8 +609,8 @@ const WMEvent snap_right(WM_EVENT_SNAP_SECONDARY); w2_state->OnWMEvent(&snap_right); EXPECT_EQ(WindowStateType::kSecondarySnapped, w2_state->GetStateType()); - EXPECT_EQ(0.5f, *w1_state->snapped_width_ratio()); - EXPECT_EQ(0.5f, *w2_state->snapped_width_ratio()); + EXPECT_EQ(0.5f, *w1_state->snap_ratio()); + EXPECT_EQ(0.5f, *w2_state->snap_ratio()); ui::test::EventGenerator* generator = GetEventGenerator(); generator->MoveMouseTo(w1->bounds().CenterPoint()); @@ -644,8 +644,8 @@ EXPECT_EQ(gfx::Rect(0, 0, 300, bottom_inset), w1->bounds()); EXPECT_EQ(WindowStateType::kSecondarySnapped, w2_state->GetStateType()); EXPECT_EQ(gfx::Rect(300, 0, 100, bottom_inset), w2->bounds()); - EXPECT_EQ(0.75f, *w1_state->snapped_width_ratio()); - EXPECT_EQ(0.25f, *w2_state->snapped_width_ratio()); + EXPECT_EQ(0.75f, *w1_state->snap_ratio()); + EXPECT_EQ(0.25f, *w2_state->snap_ratio()); // Dragging should call the WindowStateDelegate. EXPECT_EQ(HTRIGHT, window_state_delegate1->GetComponentAndReset());
diff --git a/base/files/file_path.h b/base/files/file_path.h index e11d706c..e7ef1df 100644 --- a/base/files/file_path.h +++ b/base/files/file_path.h
@@ -272,6 +272,11 @@ // - calling this function with "foo.tar.blah" will return just ".blah" // until ".*.blah" is added to the hard-coded allow-list. // + // That hard-coded allow-list is case-insensitive: ".GZ" and ".gz" are + // equivalent. However, the StringType returned is not canonicalized for + // case: "foo.TAR.bz2" input will produce ".TAR.bz2", not ".tar.bz2", and + // "bar.EXT", which is not a double-extension, will produce ".EXT". + // // The following code should always work regardless of the value of path. // new_path = path.RemoveExtension().value().append(path.Extension()); // ASSERT(new_path == path.value());
diff --git a/base/files/file_path_unittest.cc b/base/files/file_path_unittest.cc index 9e99a41..c5ba954 100644 --- a/base/files/file_path_unittest.cc +++ b/base/files/file_path_unittest.cc
@@ -771,6 +771,8 @@ { FPL("C:\\foo.bar\\.."), FPL("") }, { FPL("C:\\foo.bar\\..\\\\"), FPL("") }, #endif + { FPL("/foo/bar/baz.EXT"), FPL(".EXT") }, + { FPL("/foo/bar/baz.Ext"), FPL(".Ext") }, { FPL("/foo/bar/baz.ext"), FPL(".ext") }, { FPL("/foo/bar/baz."), FPL(".") }, { FPL("/foo/bar/baz.."), FPL(".") }, @@ -795,6 +797,7 @@ const struct UnaryTestData double_extension_cases[] = { // `kCommonDoubleExtensionSuffixes` cases. Blah is not on that allow-list. // Membership is (ASCII) case-insensitive: both ".Z" and ".z" match. + { FPL("/foo.TAR.bz2"), FPL(".TAR.bz2") }, { FPL("/foo.tar.Z"), FPL(".tar.Z") }, { FPL("/foo.tar.blah"), FPL(".blah") }, { FPL("/foo.tar.bz"), FPL(".tar.bz") },
diff --git a/build/OWNERS.setnoparent b/build/OWNERS.setnoparent index 99554a4..02d1d28 100644 --- a/build/OWNERS.setnoparent +++ b/build/OWNERS.setnoparent
@@ -28,6 +28,9 @@ # expose to the open web. file://third_party/blink/API_OWNERS +# third_party/blink/web_tests/VirtualTestSuites need special care. +file://third_party/blink/web_tests/VIRTUAL_OWNERS + # Extension related files. file://chrome/browser/extensions/component_extensions_allowlist/EXTENSION_ALLOWLIST_OWNERS file://extensions/common/api/API_OWNERS @@ -64,4 +67,4 @@ # Changes to the CQ configuration can have a significant impact on the CQ cost # and performance. Approval should be limited to a small subset of the users # that can make infra changes. -file://infra/config/groups/cq-usage/CQ_USAGE_OWNERS \ No newline at end of file +file://infra/config/groups/cq-usage/CQ_USAGE_OWNERS
diff --git a/chrome/VERSION b/chrome/VERSION index b2f39e3..96f61c8f 100644 --- a/chrome/VERSION +++ b/chrome/VERSION
@@ -1,4 +1,4 @@ MAJOR=94 MINOR=0 -BUILD=4597 +BUILD=4598 PATCH=0
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni index 0b0a7be..636ab80a8 100644 --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni
@@ -530,6 +530,8 @@ "java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java", "java/src/org/chromium/chrome/browser/download/DownloadItem.java", "java/src/org/chromium/chrome/browser/download/DownloadManagerService.java", + "java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java", + "java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java", "java/src/org/chromium/chrome/browser/download/DownloadMetrics.java", "java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java", "java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni index 4295afff..a56765d 100644 --- a/chrome/android/chrome_test_java_sources.gni +++ b/chrome/android/chrome_test_java_sources.gni
@@ -181,6 +181,7 @@ "javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java", "javatests/src/org/chromium/chrome/browser/download/DownloadLocationChangeEnd2EndTest.java", "javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java", + "javatests/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerTest.java", "javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java", "javatests/src/org/chromium/chrome/browser/download/DownloadTest.java", "javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java",
diff --git a/chrome/android/expectations/lint-suppressions.xml b/chrome/android/expectations/lint-suppressions.xml index 3746207..8563dc91 100644 --- a/chrome/android/expectations/lint-suppressions.xml +++ b/chrome/android/expectations/lint-suppressions.xml
@@ -202,6 +202,16 @@ <ignore regexp="The resource `R.string.translate_dont_offer_site` appears to be unused"/> <ignore regexp="The resource `R.string.translate_dont_offer_lang` appears to be unused"/> + <!-- crbug.com/1231370 remove this line and the following 8 lines after the bug is resolved --> + <ignore regexp="The resource `R.string.download_message_download_in_progress_description` appears to be unused"/> + <ignore regexp="The resource `R.string.download_message_download_complete_description` appears to be unused"/> + <ignore regexp="The resource `R.plurals.download_message_multiple_download_in_progress` appears to be unused"/> + <ignore regexp="The resource `R.plurals.download_message_multiple_download_complete` appears to be unused"/> + <ignore regexp="The resource `R.plurals.download_message_multiple_download_failed` appears to be unused"/> + <ignore regexp="The resource `R.plurals.download_message_multiple_download_pending` appears to be unused"/> + <ignore regexp="The resource `R.plurals.download_message_multiple_download_scheduled` appears to be unused"/> + <ignore regexp="The resource `R.string.download_message_download_scheduled_description` appears to be unused"/> + <!-- Old-style and new-style WebAPKs use same resources for simplicity. Old-style WebAPKs do not use R.style.SplashTheme but new-style WebAPKs do. TODO(crbug.com/971254): Remove suppression once old-style WebAPKs are deprecated. -->
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AccessorySheetTabViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AccessorySheetTabViewTest.java index 796d9fb..666823e 100644 --- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AccessorySheetTabViewTest.java +++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AccessorySheetTabViewTest.java
@@ -50,7 +50,7 @@ @RunWith(ChromeJUnit4ClassRunner.class) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) public class AccessorySheetTabViewTest { - private final AccessorySheetTabModel mModel = new AccessorySheetTabModel(); + private AccessorySheetTabModel mModel; private AtomicReference<RecyclerView> mView = new AtomicReference<>(); @Rule @@ -65,6 +65,7 @@ private void openLayoutInAccessorySheet( @LayoutRes int layout, KeyboardAccessoryData.Tab.Listener listener) { TestThreadUtils.runOnUiThreadBlocking(() -> { + mModel = new AccessorySheetTabModel(); AccessorySheetCoordinator accessorySheet = new AccessorySheetCoordinator(mActivityTestRule.getActivity().findViewById( R.id.keyboard_accessory_sheet_stub));
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java index 60bacba..7f24d951 100644 --- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java +++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
@@ -53,7 +53,7 @@ @RunWith(ChromeJUnit4ClassRunner.class) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) public class AddressAccessorySheetViewTest { - private final AccessorySheetTabModel mModel = new AccessorySheetTabModel(); + private AccessorySheetTabModel mModel; private AtomicReference<RecyclerView> mView = new AtomicReference<>(); @Rule @@ -63,6 +63,8 @@ public void setUp() throws InterruptedException { mActivityTestRule.startMainActivityOnBlankPage(); TestThreadUtils.runOnUiThreadBlocking(() -> { + mModel = new AccessorySheetTabModel(); + AccessorySheetCoordinator accessorySheet = new AccessorySheetCoordinator(mActivityTestRule.getActivity().findViewById( R.id.keyboard_accessory_sheet_stub));
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java index 1399cda..892ae24 100644 --- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java +++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java
@@ -51,7 +51,7 @@ @RunWith(ChromeJUnit4ClassRunner.class) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) public class PasswordAccessorySheetViewTest { - private final AccessorySheetTabModel mModel = new AccessorySheetTabModel(); + private AccessorySheetTabModel mModel; private AtomicReference<RecyclerView> mView = new AtomicReference<>(); @Rule @@ -66,6 +66,7 @@ private void openLayoutInAccessorySheet( @LayoutRes int layout, KeyboardAccessoryData.Tab.Listener listener) { TestThreadUtils.runOnUiThreadBlocking(() -> { + mModel = new AccessorySheetTabModel(); AccessorySheetCoordinator accessorySheet = new AccessorySheetCoordinator(mActivityTestRule.getActivity().findViewById( R.id.keyboard_accessory_sheet_stub));
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java index d27a327..9bd4c349 100644 --- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java +++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -1285,7 +1285,8 @@ StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile(0); TabAttributeCache.setTitleForTesting(0, "Google"); - HomepageManager.getInstance().setPrefHomepageEnabled(false); + TestThreadUtils.runOnUiThreadBlocking( + () -> HomepageManager.getInstance().setPrefHomepageEnabled(false)); Assert.assertFalse(HomepageManager.isHomepageEnabled()); // Launches Chrome and verifies that the Tab switcher is showing. @@ -1308,7 +1309,8 @@ StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile(0); TabAttributeCache.setTitleForTesting(0, "Google"); - HomepageManager.getInstance().setPrefHomepageEnabled(false); + TestThreadUtils.runOnUiThreadBlocking( + () -> HomepageManager.getInstance().setPrefHomepageEnabled(false)); Assert.assertFalse(HomepageManager.isHomepageEnabled()); // Launches Chrome and verifies that the last visited Tab is showing.
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java index ceaa1cf..7bbc5254 100644 --- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java +++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java
@@ -295,7 +295,7 @@ @Override public void tearDownTest() throws Exception { - mMCP.destroy(); + TestThreadUtils.runOnUiThreadBlocking(mMCP::destroy); super.tearDownTest(); } }
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java index 5029caaa..d985c3bd 100644 --- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java +++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
@@ -858,11 +858,12 @@ while (i < newContentList.size()) { NtpListContentManager.FeedContent content = newContentList.get(i); - // If this is an existing content, moves it to new position. + // If this is an existing content, moves it to new position, offset by header count. if (existingContentMap.containsKey(content.getKey())) { hasContentChange = true; mContentManager.moveContent( - mContentManager.findContentPositionByKey(content.getKey()), i); + mContentManager.findContentPositionByKey(content.getKey()), + i + mHeaderCount); ++i; continue; }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java index 3fbda4b..8c06113 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -104,7 +104,6 @@ import org.chromium.chrome.browser.dependency_injection.ModuleFactoryOverrides; import org.chromium.chrome.browser.device.DeviceClassManager; import org.chromium.chrome.browser.dom_distiller.DomDistillerUIUtils; -import org.chromium.chrome.browser.download.DownloadManagerService; import org.chromium.chrome.browser.download.DownloadUtils; import org.chromium.chrome.browser.download.items.OfflineContentAggregatorNotificationBridgeUiFactory; import org.chromium.chrome.browser.feature_engagement.TrackerFactory; @@ -1655,7 +1654,6 @@ mNativeInitialized = true; OfflineContentAggregatorNotificationBridgeUiFactory.instance(); maybeRemoveWindowBackground(); - DownloadManagerService.getDownloadManagerService().onActivityLaunched(); VrModuleProvider.maybeInit(); VrModuleProvider.getDelegate().onNativeLibraryAvailable();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java index a81ee8b..1a3ed473 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
@@ -68,7 +68,7 @@ * This class is responsible for tracking various updates for download items, offline items, android * downloads and computing the the state of the {@link DownloadProgressInfoBar} . */ -public class DownloadInfoBarController implements OfflineContentProvider.Observer { +public class DownloadInfoBarController implements DownloadMessageUiController { private static final String SPEEDING_UP_MESSAGE_ENABLED = "speeding_up_message_enabled"; private static final long DURATION_ACCELERATED_INFOBAR_IN_MS = 3000; private static final long DURATION_SHOW_RESULT_IN_MS = 6000; @@ -317,16 +317,12 @@ mHandler.post(() -> getOfflineContentProvider().addObserver(this)); } - /** - * Shows the message that download has started. Unlike other methods in this class, this - * method doesn't require an {@link OfflineItem} and is invoked by the backend to provide a - * responsive feedback to the users even before the download has actually started. - */ + @Override public void onDownloadStarted() { computeNextStepForUpdate(null, true, false, false); } - /** Updates the InfoBar when new information about a download comes in. */ + @Override public void onDownloadItemUpdated(DownloadItem downloadItem) { if (mUseNewDownloadPath) return; @@ -346,14 +342,13 @@ computeNextStepForUpdate(offlineItem); } - /** Updates the InfoBar after a download has been removed. */ + @Override public void onDownloadItemRemoved(ContentId contentId) { if (mUseNewDownloadPath) return; onItemRemoved(contentId); } - /** Associates a notification ID with the tracked download for future usage. */ - // TODO(shaktisahu): Find an alternative way after moving to offline content provider. + @Override public void onNotificationShown(ContentId id, int notificationId) { mNotificationIds.put(id, notificationId); } @@ -374,7 +369,6 @@ }); } - // OfflineContentProvider.Observer implementation. @Override public void onItemsAdded(List<OfflineItem> items) { for (OfflineItem item : items) { @@ -409,7 +403,7 @@ computeNextStepForUpdate(item); } - /** @return Whether the infobar is currently showing. */ + @Override public boolean isShowing() { return mCurrentInfoBar != null; } @@ -835,6 +829,7 @@ @VisibleForTesting protected void showInfoBar(@DownloadInfoBarState int state, DownloadProgressInfoBarData info) { if (!ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_INFOBAR)) return; + assert !ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE); mCurrentInfo = info;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java index 5db3edd..4743e6c 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
@@ -5,6 +5,7 @@ package org.chromium.chrome.browser.download; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.DownloadManager; import android.content.ActivityNotFoundException; import android.content.Context; @@ -55,6 +56,7 @@ import org.chromium.components.external_intents.ExternalNavigationHandler; import org.chromium.components.feature_engagement.EventConstants; import org.chromium.components.feature_engagement.Tracker; +import org.chromium.components.messages.MessageDispatcher; import org.chromium.components.offline_items_collection.ContentId; import org.chromium.components.offline_items_collection.FailState; import org.chromium.components.offline_items_collection.LegacyHelpers; @@ -67,6 +69,7 @@ import org.chromium.net.ConnectionType; import org.chromium.net.NetworkChangeNotifierAutoDetect; import org.chromium.net.RegistrationPolicyAlwaysRegister; +import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.widget.Toast; import java.io.File; @@ -142,6 +145,9 @@ private DownloadInfoBarController mInfoBarController; private HashMap<OTRProfileID, DownloadInfoBarController> mIncognitoInfoBarControllerMap = new HashMap<>(); + private DownloadMessageUiController mMessageUiController; + private HashMap<OTRProfileID, DownloadMessageUiController> mIncognitoMessageUiControllerMap = + new HashMap<>(); private long mNativeDownloadManagerService; private NetworkChangeNotifierAutoDetect mNetworkChangeNotifier; // Flag to track if we need to post a task to update download notifications. @@ -278,21 +284,22 @@ return mDownloadNotifier; } - /** @return The {@link DownloadInfoBarController} controller associated with the profile. */ - public DownloadInfoBarController getInfoBarController(OTRProfileID otrProfileID) { - if (!OTRProfileID.isOffTheRecord(otrProfileID)) return mInfoBarController; + /** @return The {@link DownloadMessageUiController} controller associated with the profile. */ + public DownloadMessageUiController getInfoBarController(OTRProfileID otrProfileID) { + boolean showMessageUi = + ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE); + if (!OTRProfileID.isOffTheRecord(otrProfileID)) { + return showMessageUi ? mMessageUiController : mInfoBarController; + } - // If the OTR profile does not exist, we should not create regarding info bar controller. + // If the OTR profile does not exist, we should not create the controller. if (!Profile.getLastUsedRegularProfile().hasOffTheRecordProfile(otrProfileID)) { return null; } - DownloadInfoBarController controller = mIncognitoInfoBarControllerMap.get(otrProfileID); - if (controller == null) { - controller = new DownloadInfoBarController(otrProfileID); - mIncognitoInfoBarControllerMap.put(otrProfileID, controller); - } - return controller; + // TODO(shaktisahu, sideyilmaz): If we have multiple OTR profiles, will this be ever null? + return showMessageUi ? mIncognitoMessageUiControllerMap.get(otrProfileID) + : mIncognitoInfoBarControllerMap.get(otrProfileID); } /** For testing only. */ @@ -400,14 +407,24 @@ * Called when browser activity is launched. For background resumption and cancellation, this * will not be called. */ - public void onActivityLaunched() { + public void onActivityLaunched(Activity activity, MessageDispatcher messageDispatcher, + ModalDialogManager modalDialogManager) { if (!mActivityLaunched) { + OTRProfileID primaryOTRProfileID = OTRProfileID.getPrimaryOTRProfileID(); + // The info bar controller for regular and primary OTR profile should be created when // activity is launched. - mInfoBarController = new DownloadInfoBarController(/*otrProfileID=*/null); - OTRProfileID primaryOTRProfileID = OTRProfileID.getPrimaryOTRProfileID(); - mIncognitoInfoBarControllerMap.put( - primaryOTRProfileID, new DownloadInfoBarController(primaryOTRProfileID)); + if (ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE)) { + mMessageUiController = new DownloadMessageUiControllerImpl( + /*otrProfileID=*/null, activity, messageDispatcher, modalDialogManager); + mIncognitoMessageUiControllerMap.put(primaryOTRProfileID, + new DownloadMessageUiControllerImpl(primaryOTRProfileID, activity, + messageDispatcher, modalDialogManager)); + } else { + mInfoBarController = new DownloadInfoBarController(/*otrProfileID=*/null); + mIncognitoInfoBarControllerMap.put( + primaryOTRProfileID, new DownloadInfoBarController(primaryOTRProfileID)); + } DownloadNotificationService.clearResumptionAttemptLeft(); @@ -418,7 +435,7 @@ } private void updateDownloadInfoBar(DownloadItem item) { - DownloadInfoBarController infobarController = + DownloadMessageUiController infobarController = getInfoBarController(item.getDownloadInfo().getOTRProfileId()); if (infobarController != null) infobarController.onDownloadItemUpdated(item); } @@ -740,7 +757,9 @@ return; } - getInfoBarController(downloadItem.getDownloadInfo().getOTRProfileId()).onDownloadStarted(); + DownloadMessageUiController infoBarController = + getInfoBarController(downloadItem.getDownloadInfo().getOTRProfileId()); + if (infoBarController != null) infoBarController.onDownloadStarted(); } @Nullable @@ -1027,7 +1046,7 @@ removeDownloadProgress(id.id); } else { mDownloadNotifier.notifyDownloadCanceled(id); - DownloadInfoBarController infoBarController = getInfoBarController(otrProfileID); + DownloadMessageUiController infoBarController = getInfoBarController(otrProfileID); if (infoBarController != null) infoBarController.onDownloadItemRemoved(id); } } @@ -1156,7 +1175,7 @@ item.setSystemDownloadId(systemDownloadId); handleAutoOpenAfterDownload(item); } else { - DownloadInfoBarController infobarController = + DownloadMessageUiController infobarController = getInfoBarController(info.getOTRProfileId()); if (infobarController != null) { infobarController.onNotificationShown(info.getContentId(), notificationId); @@ -1226,8 +1245,13 @@ && item.getDownloadInfo().hasUserGesture() && canResolve) { handleAutoOpenAfterDownload(item); } else { - getInfoBarController(item.getDownloadInfo().getOTRProfileId()) - .onItemUpdated(DownloadItem.createOfflineItem(item), null); + DownloadMessageUiController infoBarController = + getInfoBarController( + item.getDownloadInfo().getOTRProfileId()); + if (infoBarController != null) { + infoBarController.onItemUpdated( + DownloadItem.createOfflineItem(item), null); + } } } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -1559,7 +1583,7 @@ // Deprecated after new download backend. @CalledByNative private void onDownloadItemRemoved(String guid, OTRProfileID otrProfileID) { - DownloadInfoBarController infobarController = getInfoBarController(otrProfileID); + DownloadMessageUiController infobarController = getInfoBarController(otrProfileID); if (infobarController != null) { infobarController.onDownloadItemRemoved( LegacyHelpers.buildLegacyContentId(false, guid));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java new file mode 100644 index 0000000..748bbb9 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java
@@ -0,0 +1,49 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.download; + +import org.chromium.components.offline_items_collection.ContentId; +import org.chromium.components.offline_items_collection.OfflineContentProvider; +import org.chromium.components.offline_items_collection.OfflineItem; +import org.chromium.components.offline_items_collection.UpdateDelta; + +import java.util.List; + +/** + * The central class responsible for showing the download progress UI. Implemented by both the info + * bar and messages UI versions. Tracks updates for download items, offline items, android + * downloads etc. and computes the current state of the UI to be shown. + */ +public interface DownloadMessageUiController extends OfflineContentProvider.Observer { + /** + * Shows the message that download has started. Unlike other methods in this class, this + * method doesn't require an {@link OfflineItem} and is invoked by the backend to provide a + * responsive feedback to the users even before the download has actually started. + */ + void onDownloadStarted(); + + /** Updates the UI when new information about a download comes in. */ + void onDownloadItemUpdated(DownloadItem downloadItem); + + /** Updates the UI after a download has been removed. */ + void onDownloadItemRemoved(ContentId contentId); + + /** Associates a notification ID with the tracked download for future usage. */ + // TODO(shaktisahu): Find an alternative way after moving to offline content provider. + void onNotificationShown(ContentId id, int notificationId); + + /** OfflineContentProvider.Observer methods. */ + @Override + void onItemsAdded(List<OfflineItem> items); + + @Override + void onItemRemoved(ContentId id); + + @Override + void onItemUpdated(OfflineItem item, UpdateDelta updateDelta); + + /** @return Whether the UI is currently showing. */ + boolean isShowing(); +}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java new file mode 100644 index 0000000..b3c97570 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
@@ -0,0 +1,1010 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.download; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.text.TextUtils; + +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import androidx.annotation.PluralsRes; +import androidx.annotation.VisibleForTesting; +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; + +import org.chromium.base.ApiCompatibilityUtils; +import org.chromium.base.metrics.RecordHistogram; +import org.chromium.base.metrics.RecordUserAction; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.download.DownloadLaterMetrics.DownloadLaterUiEvent; +import org.chromium.chrome.browser.download.dialogs.DownloadLaterDialogHelper; +import org.chromium.chrome.browser.download.dialogs.DownloadLaterDialogHelper.Source; +import org.chromium.chrome.browser.download.items.OfflineContentAggregatorFactory; +import org.chromium.chrome.browser.flags.ChromeFeatureList; +import org.chromium.chrome.browser.profiles.OTRProfileID; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.components.download.DownloadState; +import org.chromium.components.messages.DismissReason; +import org.chromium.components.messages.MessageBannerProperties; +import org.chromium.components.messages.MessageDispatcher; +import org.chromium.components.messages.MessageIdentifier; +import org.chromium.components.offline_items_collection.ContentId; +import org.chromium.components.offline_items_collection.LegacyHelpers; +import org.chromium.components.offline_items_collection.OfflineContentProvider; +import org.chromium.components.offline_items_collection.OfflineItem; +import org.chromium.components.offline_items_collection.OfflineItemSchedule; +import org.chromium.components.offline_items_collection.OfflineItemState; +import org.chromium.components.offline_items_collection.UpdateDelta; +import org.chromium.components.prefs.PrefService; +import org.chromium.components.url_formatter.SchemeDisplay; +import org.chromium.components.url_formatter.UrlFormatter; +import org.chromium.components.user_prefs.UserPrefs; +import org.chromium.ui.modaldialog.ModalDialogManager; +import org.chromium.ui.modelutil.PropertyModel; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Message UI specific implementation of {@link DownloadMessageUiController}. + */ +public class DownloadMessageUiControllerImpl implements DownloadMessageUiController { + private static final String SPEEDING_UP_MESSAGE_ENABLED = "speeding_up_message_enabled"; + private static final long DURATION_ACCELERATED_INFOBAR_IN_MS = 3000; + private static final long DURATION_SHOW_RESULT_IN_MS = 6000; + private static final long DURATION_SHOW_RESULT_DOWNLOAD_SCHEDULED_IN_MS = 12000; + + // Values for the histogram Android.Download.InfoBar.Shown. Keep this in sync with the + // DownloadInfoBar.ShownState enum in enums.xml. + @IntDef({UmaInfobarShown.ANY_STATE, UmaInfobarShown.ACCELERATED, UmaInfobarShown.DOWNLOADING, + UmaInfobarShown.COMPLETE, UmaInfobarShown.FAILED, UmaInfobarShown.PENDING, + UmaInfobarShown.MULTIPLE_DOWNLOADING, UmaInfobarShown.MULTIPLE_COMPLETE, + UmaInfobarShown.MULTIPLE_FAILED, UmaInfobarShown.MULTIPLE_PENDING, + UmaInfobarShown.SCHEDULED, UmaInfobarShown.MULTIPLE_SCHEDULED}) + @Retention(RetentionPolicy.SOURCE) + private @interface UmaInfobarShown { + int ANY_STATE = 0; + int ACCELERATED = 1; + int DOWNLOADING = 2; + int COMPLETE = 3; + int FAILED = 4; + int PENDING = 5; + int MULTIPLE_DOWNLOADING = 6; + int MULTIPLE_COMPLETE = 7; + int MULTIPLE_FAILED = 8; + int MULTIPLE_PENDING = 9; + int SCHEDULED = 10; + int MULTIPLE_SCHEDULED = 11; + int NUM_ENTRIES = 12; + } + + /** + * Represents various UI states that the Message UI cycles through. + * Note: This enum is append-only and the values must match the DownloadInfoBarState enum in + * enums.xml. Values should be number from 0 and can't have gaps. + */ + @VisibleForTesting + @IntDef({DownloadInfoBarState.INITIAL, DownloadInfoBarState.DOWNLOADING, + DownloadInfoBarState.SHOW_RESULT, DownloadInfoBarState.CANCELLED}) + @Retention(RetentionPolicy.SOURCE) + protected @interface DownloadInfoBarState { + // Default initial state. It is also the final state after all the downloads are paused or + // removed. No UI is shown in this state. + int INITIAL = 0; + // UI is showing a message indicating the downloads in progress. In case of a single + // accelerated download, the message would show the speeding-up download message for {@code + // DURATION_ACCELERATED_INFOBAR_IN_MS} before transitioning to downloading file(s) message. + // If download completes,fails or goes to pending state, the transition happens immediately + // to SHOW_RESULT state. + int DOWNLOADING = 1; + // The message is showing download complete, failed or pending message. The message stays in + // this state for {@code DURATION_SHOW_RESULT_IN_MS} before transitioning to the next state, + // which can be another SHOW_RESULT or DOWNLOADING state. This can also happen to be the + // terminal state if there are no more updates to be shown. + // In case of a new download, completed download or cancellation signal, the transition + // happens immediately. + int SHOW_RESULT = 2; + // The state of the message after it was explicitly cancelled by the user. The message UI is + // resurfaced only when there is a new download or an existing download moves to completion, + // failed or pending state. + int CANCELLED = 3; + // Number of entries + int NUM_ENTRIES = 4; + } + + // Represents various result states shown in the message UI. + private @interface ResultState { + int INVALID = -1; + int COMPLETE = 0; + int FAILED = 1; + int PENDING = 2; + int SCHEDULED = 3; + } + + /** + * Represents the data required to show UI elements of the message. + */ + public static class DownloadProgressMessageUiData { + @Nullable + public ContentId id; + + public String message; + public String description; + public String link; + public int icon; + + // Whether the icon corresponds to a vector drawable. + public boolean hasVectorDrawable; + + // Whether the the message must be shown, even though it was dismissed earlier. This + // usually means there is a significant download update, e.g. download completed. + public boolean forceShow; + + // Keeps track of the current number of downloads in various states. + public DownloadCount downloadCount = new DownloadCount(); + + // Used for differentiating various states (e.g. completed, failed, pending etc) in the + // SHOW_RESULT state. Keeps track of the state of the currently displayed item(s) and should + // be reset to null when moving out DOWNLOADING/SHOW_RESULT state. + @ResultState + public int resultState; + + // Contains the information to change the download schedule for download later feature. + public OfflineItemSchedule schedule; + + @Override + public int hashCode() { + int result = (id == null ? 0 : id.hashCode()); + result = 31 * result + (message == null ? 0 : message.hashCode()); + result = 31 * result + (link == null ? 0 : link.hashCode()); + result = 31 * result + icon; + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!(obj instanceof DownloadProgressMessageUiData)) return false; + + DownloadProgressMessageUiData other = (DownloadProgressMessageUiData) obj; + boolean idEquality = (id == null ? other.id == null : id.equals(other.id)); + return idEquality && TextUtils.equals(message, other.message) + && TextUtils.equals(description, other.description) + && TextUtils.equals(link, other.link) && icon == other.icon; + } + + /** Called to update the value of this object from a given object. */ + public void update(DownloadProgressMessageUiData other) { + id = other.id; + message = other.message; + link = other.link; + icon = other.icon; + hasVectorDrawable = other.hasVectorDrawable; + forceShow = other.forceShow; + downloadCount = other.downloadCount; + resultState = other.resultState; + schedule = other.schedule; + } + } + + /** + * An utility class to count the number of downloads at different states at any given time. + */ + private static class DownloadCount { + public int inProgress; + public int pending; + public int failed; + public int completed; + public int scheduled; + + /** @return The total number of downloads being tracked. */ + public int totalCount() { + return inProgress + pending + failed + completed + scheduled; + } + + public int getCountForResultState(@ResultState int state) { + switch (state) { + case ResultState.COMPLETE: + return completed; + case ResultState.FAILED: + return failed; + case ResultState.PENDING: + return pending; + case ResultState.SCHEDULED: + return scheduled; + default: + assert false; + } + return 0; + } + + @Override + public int hashCode() { + int result = 31 * inProgress; + result = 31 * result + pending; + result = 31 * result + failed; + result = 31 * result + completed; + result = 31 * result + scheduled; + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!(obj instanceof DownloadCount)) return false; + + DownloadCount other = (DownloadCount) obj; + return inProgress == other.inProgress && pending == other.pending + && failed == other.failed && completed == other.completed + && scheduled == other.scheduled; + } + } + + private final boolean mUseNewDownloadPath; + private final OTRProfileID mOtrProfileID; + private final Handler mHandler = new Handler(); + + // Keeps track of a running list of items, which gets updated regularly with every update from + // the backend. The entries are removed only when the item has reached a certain completion + // state (i.e. complete, failed or pending) or is cancelled/removed from the backend. + private final LinkedHashMap<ContentId, OfflineItem> mTrackedItems = new LinkedHashMap<>(); + + // Keeps track of all the items that have been seen in the current chrome session. + private final Set<ContentId> mSeenItems = new HashSet<>(); + + // Keeps track of the items which are being ignored by the controller, e.g. user initiated + // paused items. + private final Set<ContentId> mIgnoredItems = new HashSet<>(); + + // The notification IDs associated with the currently tracked completed items. The notification + // should be removed when the message action button is clicked to open the item. + private final Map<ContentId, Integer> mNotificationIds = new HashMap<>(); + + // The current state of the message UI. + private @DownloadInfoBarState int mState = DownloadInfoBarState.INITIAL; + + // This is used when the message UI is currently in a state awaiting timer completion, e.g. + // showing the speeding-up message or showing the result of a download. This is used to schedule + // a task to determine the next state. If the message UI moves out of the current state, the + // scheduled task should be cancelled. + private Runnable mEndTimerRunnable; + + // Represents the currently displayed UI data. + private DownloadProgressMessageUiData mCurrentInfo; + + // Used to show the download later dialog to change download schedule. + private DownloadLaterDialogHelper mDownloadLaterDialogHelper; + + // The associated activity context. + private final Context mContext; + + // The message dispatcher for showing the message UI. + private final MessageDispatcher mMessageDispatcher; + + // Dialog manager used for creating download later dialogs. + private final ModalDialogManager mModalDialogManager; + + // The model used to update the UI properties. + private PropertyModel mPropertyModel; + + private Runnable mDismissRunnable; + + /** Constructor. */ + public DownloadMessageUiControllerImpl(OTRProfileID otrProfileID, Context context, + MessageDispatcher messageDispatcher, ModalDialogManager modalDialogManager) { + mUseNewDownloadPath = + ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER); + mOtrProfileID = otrProfileID; + mMessageDispatcher = messageDispatcher; + mContext = context; + mModalDialogManager = modalDialogManager; + + mHandler.post(() -> getOfflineContentProvider().addObserver(this)); + } + + /** + * Shows the message that download has started. Unlike other methods in this class, this + * method doesn't require an {@link OfflineItem} and is invoked by the backend to provide a + * responsive feedback to the users even before the download has actually started. + */ + @Override + public void onDownloadStarted() { + computeNextStepForUpdate(null, true, false, false); + } + + /** Updates the message UI when new information about a download comes in. */ + @Override + public void onDownloadItemUpdated(DownloadItem downloadItem) { + if (mUseNewDownloadPath) return; + + OfflineItem offlineItem = DownloadItem.createOfflineItem(downloadItem); + if (!isVisibleToUser(offlineItem)) return; + + if (downloadItem.getDownloadInfo().state() == DownloadState.COMPLETE) { + handleDownloadCompletion(downloadItem); + return; + } + + if (offlineItem.state == OfflineItemState.CANCELLED) { + onItemRemoved(offlineItem.id); + return; + } + + computeNextStepForUpdate(offlineItem); + } + + /** Updates the message UI after a download has been removed. */ + @Override + public void onDownloadItemRemoved(ContentId contentId) { + if (mUseNewDownloadPath) return; + onItemRemoved(contentId); + } + + /** Associates a notification ID with the tracked download for future usage. */ + // TODO(shaktisahu): Find an alternative way after moving to offline content provider. + @Override + public void onNotificationShown(ContentId id, int notificationId) { + mNotificationIds.put(id, notificationId); + } + + private void handleDownloadCompletion(DownloadItem downloadItem) { + // Multiple OnDownloadUpdated() notifications may be issued while the + // download is in the COMPLETE state. Don't handle it if it was previously not in-progress. + if (!mTrackedItems.containsKey(downloadItem.getContentId())) return; + + // If the download should be auto-opened, we shouldn't show the UI. + DownloadManagerService.getDownloadManagerService().checkIfDownloadWillAutoOpen( + downloadItem, (result) -> { + if (result) { + onItemRemoved(downloadItem.getContentId()); + } else { + computeNextStepForUpdate(DownloadItem.createOfflineItem(downloadItem)); + } + }); + } + + @Override + public void onItemsAdded(List<OfflineItem> items) { + for (OfflineItem item : items) { + if (!isVisibleToUser(item)) continue; + computeNextStepForUpdate(item); + } + } + + @Override + public void onItemRemoved(ContentId id) { + if (!mSeenItems.contains(id)) return; + + mTrackedItems.remove(id); + mNotificationIds.remove(id); + computeNextStepForUpdate(null, false, false, true); + } + + @Override + public void onItemUpdated(OfflineItem item, UpdateDelta updateDelta) { + if (!isVisibleToUser(item)) return; + + if (updateDelta != null && !updateDelta.stateChanged + && item.state == OfflineItemState.COMPLETE) { + return; + } + + if (item.state == OfflineItemState.CANCELLED) { + onItemRemoved(item.id); + return; + } + + computeNextStepForUpdate(item); + } + + /** @return Whether the message is currently showing. */ + @Override + public boolean isShowing() { + return mPropertyModel != null; + } + + private boolean isVisibleToUser(OfflineItem offlineItem) { + // Need to use serialized OTRProfileID for comparison, since calling + // |OTRProfileID#deserialize| method causes crash if the OTR profile is destroyed. + String stringOTRProfileID = OTRProfileID.serialize(mOtrProfileID); + if (offlineItem.isTransient + || !OTRProfileID.areEqual(stringOTRProfileID, offlineItem.otrProfileId) + || offlineItem.isSuggested || offlineItem.isDangerous) { + return false; + } + if (LegacyHelpers.isLegacyDownload(offlineItem.id) + && TextUtils.isEmpty(offlineItem.filePath)) { + return false; + } + + if (MimeUtils.canAutoOpenMimeType(offlineItem.mimeType)) { + return false; + } + + return true; + } + + private void computeNextStepForUpdate(OfflineItem updatedItem) { + computeNextStepForUpdate(updatedItem, false, false, false); + } + + /** + * Updates the state of the UI based on the update received and current state of the + * tracked downloads. + * @param updatedItem The item that was updated just now. + * @param forceShowDownloadStarted Whether the message should show download started even if + * there is no updated item. + * @param userCancel Whether the message was cancelled just now. + * ended. + */ + private void computeNextStepForUpdate(OfflineItem updatedItem, boolean forceShowDownloadStarted, + boolean userCancel, boolean itemWasRemoved) { + if (!ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_INFOBAR)) return; + + if (updatedItem != null && mIgnoredItems.contains(updatedItem.id)) return; + + preProcessUpdatedItem(updatedItem); + boolean isNewDownload = forceShowDownloadStarted + || (updatedItem != null && updatedItem.state == OfflineItemState.IN_PROGRESS + && updatedItem.schedule == null && !mSeenItems.contains(updatedItem.id)); + boolean itemResumedFromPending = itemResumedFromPending(updatedItem); + + if (updatedItem != null) { + mTrackedItems.put(updatedItem.id, updatedItem); + mSeenItems.add(updatedItem.id); + } + + boolean itemWasPaused = updatedItem != null && updatedItem.state == OfflineItemState.PAUSED; + if (itemWasPaused) { + mIgnoredItems.add(updatedItem.id); + mTrackedItems.remove(updatedItem.id); + } + + DownloadCount downloadCount = getDownloadCount(); + + boolean shouldShowResult = (downloadCount.completed + downloadCount.failed + + downloadCount.pending + downloadCount.scheduled) + > 0; + + boolean shouldShowAccelerating = + mEndTimerRunnable != null && mState == DownloadInfoBarState.DOWNLOADING; + + @DownloadInfoBarState + int nextState = mState; + switch (mState) { + case DownloadInfoBarState.INITIAL: // Intentional fallthrough. + case DownloadInfoBarState.CANCELLED: + if (isNewDownload) { + nextState = DownloadInfoBarState.DOWNLOADING; + shouldShowAccelerating = + isAccelerated(updatedItem) && downloadCount.inProgress == 1; + } else if (shouldShowResult) { + nextState = DownloadInfoBarState.SHOW_RESULT; + } + break; + case DownloadInfoBarState.DOWNLOADING: + if (isNewDownload) shouldShowAccelerating = false; + + if (shouldShowResult) { + nextState = DownloadInfoBarState.SHOW_RESULT; + } else if (itemWasPaused || itemWasRemoved) { + nextState = downloadCount.inProgress == 0 ? DownloadInfoBarState.INITIAL + : DownloadInfoBarState.DOWNLOADING; + } + break; + case DownloadInfoBarState.SHOW_RESULT: + if (isNewDownload) { + nextState = DownloadInfoBarState.DOWNLOADING; + shouldShowAccelerating = + isAccelerated(updatedItem) && downloadCount.inProgress == 1; + } else if (!shouldShowResult) { + if (mEndTimerRunnable == null && downloadCount.inProgress > 0) { + nextState = DownloadInfoBarState.DOWNLOADING; + } + + boolean currentlyShowingPending = + mCurrentInfo != null && mCurrentInfo.resultState == ResultState.PENDING; + if (currentlyShowingPending && itemResumedFromPending) { + nextState = DownloadInfoBarState.DOWNLOADING; + } + if ((itemWasPaused || itemWasRemoved) && mTrackedItems.size() == 0) { + nextState = DownloadInfoBarState.INITIAL; + } + } + break; + } + + if (userCancel) nextState = DownloadInfoBarState.CANCELLED; + + moveToState(nextState, shouldShowAccelerating); + } + + private void moveToState(@DownloadInfoBarState int nextState, boolean showAccelerating) { + boolean closePreviousMessage = nextState == DownloadInfoBarState.INITIAL + || nextState == DownloadInfoBarState.CANCELLED; + if (closePreviousMessage) { + mCurrentInfo = null; + closePreviousMessage(); + if (nextState == DownloadInfoBarState.INITIAL) { + mTrackedItems.clear(); + } else { + clearFinishedItems(ResultState.COMPLETE, ResultState.FAILED, ResultState.PENDING, + ResultState.SCHEDULED); + } + clearEndTimerRunnable(); + } + + if (nextState == DownloadInfoBarState.DOWNLOADING + || nextState == DownloadInfoBarState.SHOW_RESULT) { + int resultState = findOfflineItemStateForInfoBarState(nextState); + if (resultState == ResultState.INVALID) { + // This is expected in the terminal SHOW_RESULT state when we have cleared the + // tracked items but still want to show the infobar indefinitely. + return; + } + createMessageForState(nextState, resultState, showAccelerating); + } + + mState = nextState; + } + + /** + * Determines the {@link OfflineItemState} for the message to be shown on the message. For + * DOWNLOADING state, it will return {@link OfflineItemState#IN_PROGRESS}. Otherwise it should + * show the result state which can be complete, failed or pending. There is usually a delay of + * DURATION_SHOW_RESULT_IN_MS between transition between these states, except for the complete + * state which must be shown as soon as received. While the UI is in one of these states, + * if we get another download update for the same state, we incorporate that in the existing + * message and reset the timer to another full duration. Updates for pending and failed would be + * shown in the order received. + */ + private @ResultState int findOfflineItemStateForInfoBarState( + @DownloadInfoBarState int infoBarState) { + if (infoBarState == DownloadInfoBarState.DOWNLOADING) return OfflineItemState.IN_PROGRESS; + + assert infoBarState == DownloadInfoBarState.SHOW_RESULT; + + DownloadCount downloadCount = getDownloadCount(); + + // If there are completed downloads, show immediately. + if (downloadCount.completed > 0) return ResultState.COMPLETE; + if (downloadCount.scheduled > 0) return ResultState.SCHEDULED; + + // If the infobar is already showing this state, just add this item to the same state. + int previousResultState = + mCurrentInfo != null ? mCurrentInfo.resultState : ResultState.INVALID; + if (previousResultState != ResultState.INVALID + && downloadCount.getCountForResultState(previousResultState) > 0) { + return previousResultState; + } + + // Show any failed or pending states in the order they were received. + for (OfflineItem item : mTrackedItems.values()) { + int resultState = fromOfflineItemState(item); + if (resultState != ResultState.INVALID) return resultState; + } + + return ResultState.INVALID; + } + + /** + * Prepares the message to show the next state. This includes setting the message title, + * description, icon, and action. + * @param uiState The UI state to be shown. + * @param resultState The state of the corresponding offline items to be shown. + */ + private void createMessageForState(@DownloadInfoBarState int uiState, + @ResultState int resultState, boolean showAccelerating) { + DownloadProgressMessageUiData info = new DownloadProgressMessageUiData(); + + @PluralsRes + int stringRes = -1; + if (uiState == DownloadInfoBarState.DOWNLOADING) { + stringRes = R.plurals.download_message_multiple_download_in_progress; + info.icon = R.drawable.infobar_downloading; + } else if (resultState == ResultState.COMPLETE) { + stringRes = R.plurals.download_message_multiple_download_complete; + info.icon = R.drawable.infobar_download_complete; + info.hasVectorDrawable = true; + } else if (resultState == ResultState.FAILED) { + stringRes = R.plurals.download_message_multiple_download_failed; + info.icon = R.drawable.ic_error_outline_googblue_24dp; + } else if (resultState == ResultState.PENDING) { + stringRes = R.plurals.download_message_multiple_download_pending; + info.icon = R.drawable.ic_error_outline_googblue_24dp; + } else if (resultState == ResultState.SCHEDULED) { + stringRes = R.plurals.download_message_multiple_download_scheduled; + info.icon = R.drawable.ic_file_download_scheduled_24dp; + } else { + assert false : "Unexpected resultState " + resultState + " and infoBarState " + uiState; + } + + OfflineItem itemToShow = null; + for (OfflineItem item : mTrackedItems.values()) { + if (fromOfflineItemState(item) != resultState) continue; + itemToShow = item; + } + + DownloadCount downloadCount = getDownloadCount(); + if (uiState == DownloadInfoBarState.DOWNLOADING) { + int inProgressDownloadCount = + downloadCount.inProgress == 0 ? 1 : downloadCount.inProgress; + info.message = getContext().getResources().getQuantityString( + stringRes, inProgressDownloadCount, inProgressDownloadCount); + info.description = getContext().getString( + R.string.download_message_download_in_progress_description); + + info.link = showAccelerating ? null : getContext().getString(R.string.details_link); + } else if (uiState == DownloadInfoBarState.SHOW_RESULT) { + int itemCount = getDownloadCount().getCountForResultState(resultState); + boolean singleDownloadCompleted = itemCount == 1 && resultState == ResultState.COMPLETE; + boolean singleDownloadScheduled = + itemCount == 1 && resultState == ResultState.SCHEDULED; + info.message = + getContext().getResources().getQuantityString(stringRes, itemCount, itemCount); + if (singleDownloadCompleted) { + String bytesString = + org.chromium.components.browser_ui.util.DownloadUtils.getStringForBytes( + getContext(), itemToShow.totalSizeBytes); + String displayUrl = UrlFormatter.formatUrlForSecurityDisplay( + itemToShow.url, SchemeDisplay.OMIT_HTTP_AND_HTTPS); + info.description = getContext().getString( + R.string.download_message_download_complete_description, bytesString, + displayUrl); + info.id = itemToShow.id; + info.link = getContext().getString(R.string.open_downloaded_label); + info.icon = R.drawable.infobar_download_complete_animation; + } else if (singleDownloadScheduled) { + // TODO(shaktisahu, xingliu): Find out what the message should be. + info.description = getContext().getString( + R.string.download_message_download_scheduled_description); + info.link = getContext().getString(R.string.change_link); + info.id = itemToShow.id; + info.schedule = itemToShow.schedule.clone(); + } else { + // TODO(shaktisahu): Incorporate various types of failure messages. + // TODO(shaktisahu, xingliu): Consult UX to handle multiple schedule variations. + info.link = getContext().getString(R.string.details_link); + } + } + + info.resultState = resultState; + + if (info.equals(mCurrentInfo)) return; + + boolean startTimer = showAccelerating || uiState == DownloadInfoBarState.SHOW_RESULT; + + clearEndTimerRunnable(); + + if (startTimer) { + long delay = getDelayToNextStep(showAccelerating, resultState); + mEndTimerRunnable = () -> { + mEndTimerRunnable = null; + if (mCurrentInfo != null) mCurrentInfo.resultState = ResultState.INVALID; + if (uiState == DownloadInfoBarState.SHOW_RESULT) { + clearFinishedItems(resultState); + } + computeNextStepForUpdate(null, false, false, false); + }; + mHandler.postDelayed(mEndTimerRunnable, delay); + } + + setForceShow(info); + showMessage(uiState, info); + } + + private void setForceShow(DownloadProgressMessageUiData info) { + info.downloadCount = getDownloadCount(); + info.forceShow = !info.downloadCount.equals( + mCurrentInfo == null ? null : mCurrentInfo.downloadCount); + + // TODO(xingliu, shaktisahu): downloadCount may not be updated at the correct time, see + // https://crbug.com/1127522. For now, scheduled download will always show in new tabs. + if (info.downloadCount.scheduled > 0) { + info.forceShow = true; + } + } + private void clearEndTimerRunnable() { + mHandler.removeCallbacks(mEndTimerRunnable); + mEndTimerRunnable = null; + } + + private void preProcessUpdatedItem(OfflineItem updatedItem) { + if (updatedItem == null) return; + + // INTERRUPTED downloads should be treated as PENDING in the UI. From here onwards, + // there should be no INTERRUPTED state in the core logic. + if (updatedItem.state == OfflineItemState.INTERRUPTED) { + updatedItem.state = OfflineItemState.PENDING; + } + } + + private boolean itemResumedFromPending(OfflineItem updatedItem) { + if (updatedItem == null || !mTrackedItems.containsKey(updatedItem.id)) return false; + + return mTrackedItems.get(updatedItem.id).state == OfflineItemState.PENDING + && updatedItem.state == OfflineItemState.IN_PROGRESS; + } + + @VisibleForTesting + protected long getDelayToNextStep(boolean showAccelerating, @ResultState int resultState) { + if (showAccelerating) return DURATION_ACCELERATED_INFOBAR_IN_MS; + + // Scheduled download uses a longer delay to reset tracking downloads states. + return resultState == ResultState.SCHEDULED ? DURATION_SHOW_RESULT_DOWNLOAD_SCHEDULED_IN_MS + : DURATION_SHOW_RESULT_IN_MS; + } + + @VisibleForTesting + protected boolean isSpeedingUpMessageEnabled() { + return ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean( + ChromeFeatureList.DOWNLOAD_PROGRESS_INFOBAR, SPEEDING_UP_MESSAGE_ENABLED, false); + } + + private boolean isAccelerated(OfflineItem offlineItem) { + return isSpeedingUpMessageEnabled() && offlineItem != null && offlineItem.isAccelerated; + } + + /** + * Central function called to show the message UI. If the previous message has been dismissed, + * it will be recreated only if |info.forceShow| is true. If the message hasn't been dismissed, + * it will be simply updated. + * @param state The state of the message to be shown. + * @param info Contains the information to be displayed in the UI. + */ + @VisibleForTesting + protected void showMessage( + @DownloadInfoBarState int state, DownloadProgressMessageUiData info) { + assert ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE); + recordInfoBarState(state, info); + + boolean shouldShowMessage = info.forceShow || (mPropertyModel != null); + if (!shouldShowMessage) return; + + Drawable drawable = info.hasVectorDrawable + ? VectorDrawableCompat.create( + getContext().getResources(), info.icon, getContext().getTheme()) + : ApiCompatibilityUtils.getDrawable(getContext().getResources(), info.icon); + + boolean updateOnly = mPropertyModel != null; + if (mPropertyModel == null) { + mPropertyModel = new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS) + .with(MessageBannerProperties.MESSAGE_IDENTIFIER, + MessageIdentifier.DOWNLOAD_PROGRESS) + .build(); + } + + mPropertyModel.set(MessageBannerProperties.ICON, drawable); + mPropertyModel.set(MessageBannerProperties.TITLE, info.message); + mPropertyModel.set(MessageBannerProperties.DESCRIPTION, info.description); + mPropertyModel.set(MessageBannerProperties.PRIMARY_BUTTON_TEXT, info.link); + mPropertyModel.set(MessageBannerProperties.ON_DISMISSED, this::onMessageDismissed); + mPropertyModel.set(MessageBannerProperties.ON_PRIMARY_ACTION, + () -> onPrimaryAction(info.id, info.schedule)); + mDismissRunnable = () -> { + mMessageDispatcher.dismissMessage(mPropertyModel, DismissReason.SCOPE_DESTROYED); + }; + + if (updateOnly) return; + mMessageDispatcher.enqueueWindowScopedMessage(mPropertyModel, /*highPriority=*/false); + recordInfoBarCreated(); + } + + @VisibleForTesting + protected void closePreviousMessage() { + if (mDismissRunnable != null) mDismissRunnable.run(); + mPropertyModel = null; + } + + private Context getContext() { + return mContext; + } + + private DownloadCount getDownloadCount() { + DownloadCount downloadCount = new DownloadCount(); + for (OfflineItem item : mTrackedItems.values()) { + if (item.schedule != null) { + downloadCount.scheduled++; + continue; + } + + switch (item.state) { + case OfflineItemState.IN_PROGRESS: + downloadCount.inProgress++; + break; + case OfflineItemState.COMPLETE: + downloadCount.completed++; + break; + case OfflineItemState.FAILED: + downloadCount.failed++; + break; + case OfflineItemState.CANCELLED: + break; + case OfflineItemState.PENDING: + downloadCount.pending++; + break; + case OfflineItemState.INTERRUPTED: // intentional fall through + case OfflineItemState.PAUSED: // intentional fall through + default: + assert false; + } + } + + return downloadCount; + } + + /** + * Clears the items in finished state, i.e. completed, failed or pending. + * @param states States to be removed. + */ + private void clearFinishedItems(Integer... states) { + Set<Integer> statesToRemove = new HashSet<>(Arrays.asList(states)); + List<ContentId> idsToRemove = new ArrayList<>(); + for (ContentId id : mTrackedItems.keySet()) { + OfflineItem item = mTrackedItems.get(id); + if (item == null) continue; + for (Integer stateToRemove : statesToRemove) { + if (stateToRemove == fromOfflineItemState(item)) { + idsToRemove.add(id); + break; + } + } + } + + for (ContentId id : idsToRemove) { + mTrackedItems.remove(id); + mNotificationIds.remove(id); + } + } + + private @ResultState int fromOfflineItemState(OfflineItem offlineItem) { + if (offlineItem.schedule != null) return ResultState.SCHEDULED; + + switch (offlineItem.state) { + case OfflineItemState.COMPLETE: + return ResultState.COMPLETE; + case OfflineItemState.FAILED: + return ResultState.FAILED; + case OfflineItemState.PENDING: + return ResultState.PENDING; + default: + return ResultState.INVALID; + } + } + + private OfflineContentProvider getOfflineContentProvider() { + return OfflineContentAggregatorFactory.get(); + } + + private void removeNotification(ContentId contentId) { + if (!mNotificationIds.containsKey(contentId)) return; + + DownloadInfo downloadInfo = new DownloadInfo.Builder().setContentId(contentId).build(); + DownloadManagerService.getDownloadManagerService() + .getDownloadNotifier() + .removeDownloadNotification(mNotificationIds.get(contentId), downloadInfo); + mNotificationIds.remove(contentId); + } + + private void onPrimaryAction(ContentId itemId, final OfflineItemSchedule schedule) { + mTrackedItems.remove(itemId); + removeNotification(itemId); + if (itemId != null && schedule != null) { + onChangeScheduleClicked(itemId, schedule); + } else if (itemId != null) { + DownloadUtils.openItem(itemId, mOtrProfileID, + DownloadOpenSource.DOWNLOAD_PROGRESS_MESSAGE, getContext()); + recordLinkClicked(true /*openItem*/); + } else { + DownloadManagerService.openDownloadsPage( + getContext(), mOtrProfileID, DownloadOpenSource.DOWNLOAD_PROGRESS_MESSAGE); + recordLinkClicked(false /*openItem*/); + } + } + + private void onMessageDismissed(Integer dismissReason) { + mPropertyModel = null; + if (dismissReason == DismissReason.GESTURE) { + recordCloseButtonClicked(); + computeNextStepForUpdate(null, false, true, false); + } + } + + private void onChangeScheduleClicked( + final ContentId id, final OfflineItemSchedule currentSchedule) { + if (mDownloadLaterDialogHelper != null) mDownloadLaterDialogHelper.destroy(); + + PrefService prefService = UserPrefs.get(Profile.getLastUsedRegularProfile()); + // Show the download later dialog to let the user change download schedule. + mDownloadLaterDialogHelper = + DownloadLaterDialogHelper.create(getContext(), mModalDialogManager, prefService); + DownloadLaterMetrics.recordDownloadLaterUiEvent( + DownloadLaterUiEvent.DOWNLOAD_INFOBAR_CHANGE_SCHEDULE_CLICKED); + mDownloadLaterDialogHelper.showChangeScheduleDialog( + currentSchedule, Source.DOWNLOAD_INFOBAR, (newSchedule) -> { + if (newSchedule == null) return; + if (mUseNewDownloadPath) { + OfflineContentAggregatorFactory.get().changeSchedule(id, newSchedule); + } else { + DownloadManagerService.getDownloadManagerService().changeSchedule( + id, newSchedule, mOtrProfileID); + } + }); + } + + private void recordInfoBarState( + @DownloadInfoBarState int state, DownloadProgressMessageUiData info) { + int shownState = -1; + int multipleDownloadState = -1; + if (state == DownloadInfoBarState.DOWNLOADING) { + shownState = mEndTimerRunnable != null + ? UmaInfobarShown.ACCELERATED + : (info.downloadCount.inProgress == 1 ? UmaInfobarShown.DOWNLOADING + : UmaInfobarShown.MULTIPLE_DOWNLOADING); + } else if (state == DownloadInfoBarState.SHOW_RESULT) { + switch (info.resultState) { + case ResultState.COMPLETE: + shownState = info.downloadCount.completed == 1 + ? UmaInfobarShown.COMPLETE + : UmaInfobarShown.MULTIPLE_COMPLETE; + break; + case ResultState.FAILED: + shownState = info.downloadCount.failed == 1 ? UmaInfobarShown.FAILED + : UmaInfobarShown.MULTIPLE_FAILED; + break; + case ResultState.PENDING: + shownState = info.downloadCount.pending == 1 ? UmaInfobarShown.PENDING + : UmaInfobarShown.MULTIPLE_PENDING; + break; + case ResultState.SCHEDULED: + shownState = info.downloadCount.scheduled == 1 + ? UmaInfobarShown.SCHEDULED + : UmaInfobarShown.MULTIPLE_SCHEDULED; + break; + default: + assert false : "Unexpected state " + info.resultState; + break; + } + } + + assert shownState != -1 : "Invalid state " + state; + + RecordHistogram.recordEnumeratedHistogram( + "Android.Download.InfoBar.Shown", shownState, UmaInfobarShown.NUM_ENTRIES); + RecordHistogram.recordEnumeratedHistogram("Android.Download.InfoBar.Shown", + UmaInfobarShown.ANY_STATE, UmaInfobarShown.NUM_ENTRIES); + if (multipleDownloadState != -1) { + RecordHistogram.recordEnumeratedHistogram("Android.Download.InfoBar.Shown", + multipleDownloadState, UmaInfobarShown.NUM_ENTRIES); + } + } + + private void recordInfoBarCreated() { + RecordUserAction.record("Android.Download.InfoBar.Shown"); + } + + private void recordCloseButtonClicked() { + RecordUserAction.record("Android.Download.InfoBar.CloseButtonClicked"); + RecordHistogram.recordEnumeratedHistogram("Android.Download.InfoBar.CloseButtonClicked", + mState, DownloadInfoBarState.NUM_ENTRIES); + } + + private void recordLinkClicked(boolean openItem) { + if (openItem) { + RecordUserAction.record("Android.Download.InfoBar.LinkClicked.OpenDownload"); + } else { + RecordUserAction.record("Android.Download.InfoBar.LinkClicked.OpenDownloadHome"); + } + } +} \ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java index fdd3baa..7edcf0a7 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java
@@ -134,7 +134,7 @@ } private boolean isShowingDownloadInfoBar(OTRProfileID otrProfileID) { - DownloadInfoBarController infoBarController = + DownloadMessageUiController infoBarController = DownloadManagerService.getDownloadManagerService().getInfoBarController( otrProfileID); return infoBarController == null ? false : infoBarController.isShowing();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java index 6f6cb8b..6984d1e 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
@@ -864,7 +864,7 @@ } private void showDownloadOnInfoBar(DownloadItem downloadItem, int downloadStatus) { - DownloadInfoBarController infobarController = + DownloadMessageUiController infobarController = DownloadManagerService.getDownloadManagerService().getInfoBarController( downloadItem.getDownloadInfo().getOTRProfileId()); if (infobarController == null) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/IPHBubbleDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/IPHBubbleDelegateImpl.java index a1b856eb..16d7712 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/IPHBubbleDelegateImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/IPHBubbleDelegateImpl.java
@@ -69,8 +69,9 @@ Profile profile = IncognitoUtils.getProfileFromWindowAndroid( mTab.getWindowAndroid(), mTab.isIncognito()); DownloadInfoBarController controller = - DownloadManagerService.getDownloadManagerService().getInfoBarController( - profile.getOTRProfileID()); + (DownloadInfoBarController) DownloadManagerService + .getDownloadManagerService() + .getInfoBarController(profile.getOTRProfileID()); return controller != null ? controller.getTrackerParameters() : null; case InfoBarIdentifier.GROUPED_PERMISSION_INFOBAR_DELEGATE_ANDROID: if (PermissionSettingsBridge.shouldShowNotificationsPromo(mTab.getWebContents())) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java index b17b4f1..ccaba10 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -42,6 +42,7 @@ import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager; import org.chromium.chrome.browser.crash.PureJavaExceptionReporter; import org.chromium.chrome.browser.directactions.DirectActionInitializer; +import org.chromium.chrome.browser.download.DownloadManagerService; import org.chromium.chrome.browser.feature_engagement.TrackerFactory; import org.chromium.chrome.browser.findinpage.FindToolbarManager; import org.chromium.chrome.browser.findinpage.FindToolbarObserver; @@ -523,6 +524,8 @@ mMessageDispatcher.setDelegate(mMessageQueueMediator); MessagesFactory.attachMessageDispatcher(mWindowAndroid, mMessageDispatcher); } + DownloadManagerService.getDownloadManagerService().onActivityLaunched( + mActivity, mMessageDispatcher, mActivity.getModalDialogManager()); } /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java index 962b88b7..ff75627 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java
@@ -23,6 +23,7 @@ import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeTabbedActivityTestRule; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.net.test.EmbeddedTestServer; /** Test suite for navigator.getInstalledRelatedApps functionality. */ @@ -78,12 +79,12 @@ mTab = mActivityTestRule.getActivity().getActivityTab(); mUpdateWaiter = new InstalledAppUpdateWaiter(); - mTab.addObserver(mUpdateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> mTab.addObserver(mUpdateWaiter)); } @After public void tearDown() { - mTab.removeObserver(mUpdateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> mTab.removeObserver(mUpdateWaiter)); mTestServer.stopAndDestroyServer(); }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java index 94474cd..5adc101 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java
@@ -355,7 +355,7 @@ } }; Tab tab = mActivityTestRule.getActivity().getActivityTab(); - tab.addObserver(onPageLoadStartedObserver); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(onPageLoadStartedObserver)); DOMUtils.clickNode(tab.getWebContents(), "aboutLink"); ChromeTabUtils.waitForTabPageLoaded(tab, url2); Assert.assertEquals("Desired Link not open", url2, @@ -642,7 +642,7 @@ } }; Tab tab = mActivityTestRule.getActivity().getActivityTab(); - tab.addObserver(onPageLoadStartedObserver); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(onPageLoadStartedObserver)); DOMUtils.clickNode(tab.getWebContents(), "rendererInitiated"); ChromeTabUtils.waitForTabPageLoaded(tab, finalUrl); }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java index 3126972..986ac6a 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java
@@ -59,7 +59,7 @@ public void setUp() throws Exception { mActivityTestRule.startMainActivityOnBlankPage(); mTab = mActivityTestRule.getActivity().getActivityTab(); - mTab.addObserver(mTabObserver); + TestThreadUtils.runOnUiThreadBlocking(() -> mTab.addObserver(mTabObserver)); mOnTitleUpdatedHelper = new CallbackHelper(); }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java index bfa017e..ff0290bc5 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java
@@ -115,10 +115,12 @@ ThemeColorWebContentsObserver colorObserver = new ThemeColorWebContentsObserver(); CallbackHelper themeColorHelper = colorObserver.getCallbackHelper(); - mActivityTestRule.getActivity() - .getRootUiCoordinatorForTesting() - .getTopUiThemeColorProvider() - .addThemeColorObserver(colorObserver); + TestThreadUtils.runOnUiThreadBlocking(() -> { + mActivityTestRule.getActivity() + .getRootUiCoordinatorForTesting() + .getTopUiThemeColorProvider() + .addThemeColorObserver(colorObserver); + }); // Navigate to a themed page. int curCallCount = themeColorHelper.getCallCount();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.java index f95c885e..5eb9b79 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.java
@@ -91,7 +91,9 @@ @After public void tearDown() { if (mTestServer != null) mTestServer.stopAndDestroyServer(); - BackgroundSyncBackgroundTaskScheduler.getInstance().removeObserver(mSchedulerObserver); + TestThreadUtils.runOnUiThreadBlocking(() -> { + BackgroundSyncBackgroundTaskScheduler.getInstance().removeObserver(mSchedulerObserver); + }); } @Test @@ -186,6 +188,8 @@ } }; - BackgroundSyncBackgroundTaskScheduler.getInstance().addObserver(mSchedulerObserver); + TestThreadUtils.runOnUiThreadBlocking(() -> { + BackgroundSyncBackgroundTaskScheduler.getInstance().addObserver(mSchedulerObserver); + }); } }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java index 2cde8b7..d3ee413 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
@@ -1589,7 +1589,9 @@ (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView; toggleSelectionAndEndAnimation(googleId, row); CallbackHelper helper = new CallbackHelper(); - mManager.getSelectionDelegate().addObserver((x) -> { helper.notifyCalled(); }); + TestThreadUtils.runOnUiThreadBlocking(() -> { + mManager.getSelectionDelegate().addObserver((x) -> helper.notifyCalled()); + }); removeBookmark(googleId); @@ -1627,7 +1629,10 @@ (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(0).itemView; toggleSelectionAndEndAnimation(aId, aRow); CallbackHelper helper = new CallbackHelper(); - mManager.getSelectionDelegate().addObserver((x) -> { helper.notifyCalled(); }); + + TestThreadUtils.runOnUiThreadBlocking(() -> { + mManager.getSelectionDelegate().addObserver((x) -> helper.notifyCalled()); + }); removeBookmark(googleId);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java index cda8dc60..7caa0ea 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java
@@ -35,6 +35,7 @@ */ @RunWith(ChromeJUnit4ClassRunner.class) @Features.EnableFeatures(ChromeFeatureList.DOWNLOAD_PROGRESS_INFOBAR) +@Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE) @Batch(Batch.PER_CLASS) @Batch.SplitByFeature public class DownloadInfoBarControllerTest {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java index 778298b..9ae67bfa 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java
@@ -345,7 +345,7 @@ createDownloadManagerService(notifier, UPDATE_DELAY_FOR_TEST); TestThreadUtils.runOnUiThreadBlocking( (Runnable) () -> DownloadManagerService.setDownloadManagerService(mService)); - DownloadInfoBarController infoBarController = + DownloadMessageUiController infoBarController = mService.getInfoBarController(/*otrProfileID=*/null); // Try calling download completed directly. DownloadInfo successful = getDownloadInfo();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerTest.java new file mode 100644 index 0000000..fc66c5f0 --- /dev/null +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerTest.java
@@ -0,0 +1,446 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.download; + +import android.support.test.InstrumentationRegistry; + +import androidx.test.filters.SmallTest; + +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.util.Batch; +import org.chromium.base.test.util.Criteria; +import org.chromium.base.test.util.CriteriaHelper; +import org.chromium.base.test.util.Feature; +import org.chromium.chrome.browser.flags.ChromeFeatureList; +import org.chromium.chrome.test.ChromeBrowserTestRule; +import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.chrome.test.util.browser.Features; +import org.chromium.components.offline_items_collection.LegacyHelpers; +import org.chromium.components.offline_items_collection.OfflineItem; +import org.chromium.components.offline_items_collection.OfflineItemSchedule; +import org.chromium.components.offline_items_collection.OfflineItemState; +import org.chromium.content_public.browser.test.util.TestThreadUtils; + +import java.util.UUID; + +/** + * Test class to validate that the {@link DownloadMessageUiController} correctly represents the + * state of the downloads in the current chrome session. + */ +@RunWith(ChromeJUnit4ClassRunner.class) +@Features.EnableFeatures(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE) +@Batch(Batch.PER_CLASS) +@Batch.SplitByFeature +public class DownloadMessageUiControllerTest { + @Rule + public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); + + private static final String MESSAGE_DOWNLOADING_FILE = "Downloading file…"; + private static final String MESSAGE_DOWNLOADING_TWO_FILES = "Downloading 2 files…"; + private static final String MESSAGE_SINGLE_DOWNLOAD_COMPLETE = "File downloaded"; + private static final String MESSAGE_TWO_DOWNLOAD_COMPLETE = "2 downloads complete"; + private static final String MESSAGE_DOWNLOAD_FAILED = "1 download failed"; + private static final String MESSAGE_TWO_DOWNLOAD_FAILED = "2 downloads failed"; + private static final String MESSAGE_DOWNLOAD_PENDING = "1 download pending"; + private static final String MESSAGE_TWO_DOWNLOAD_PENDING = "2 downloads pending"; + private static final String MESSAGE_DOWNLOAD_SCHEDULED_WIFI = "1 download scheduled"; + private static final String MESSAGE_TWO_DOWNLOAD_SCHEDULED = "2 downloads scheduled"; + + private static final String DESCRIPTION_DOWNLOADING = + "You can see the download status in your notifications"; + private static final String DESCRIPTION_DOWNLOAD_COMPLETE = "(0.01 KB)\nexample.com"; + private static final String DESCRIPTION_DOWNLOAD_SCHEDULED = + "Download will start when on Wi-Fi"; + + private static final String TEST_FILE_NAME = "TestFile"; + private static final long TEST_TO_NEXT_STEP_DELAY = 100; + + private TestDownloadMessageUiController mTestController; + + @Before + public void before() { + TestThreadUtils.runOnUiThreadBlocking( + () -> { mTestController = new TestDownloadMessageUiController(); }); + } + + static class TestDownloadMessageUiController extends DownloadMessageUiControllerImpl { + private DownloadProgressMessageUiData mInfo; + + public TestDownloadMessageUiController() { + super(/*otrProfileID=*/null, InstrumentationRegistry.getTargetContext(), + /*messageDispatcher=*/null, null); + } + + @Override + protected void showMessage( + @DownloadInfoBarState int state, DownloadProgressMessageUiData info) { + mInfo = info; + } + + @Override + protected void closePreviousMessage() { + mInfo = null; + } + + @Override + protected long getDelayToNextStep(boolean showAccelerating, int resultState) { + return TEST_TO_NEXT_STEP_DELAY; + } + + @Override + protected boolean isSpeedingUpMessageEnabled() { + return true; + } + + public void onItemUpdated(OfflineItem item) { + super.onItemUpdated(item.clone(), null); + } + + void verify(String message, String description) { + Assert.assertNotNull(mInfo); + Assert.assertEquals(message, mInfo.message); + Assert.assertEquals(description, mInfo.description); + } + + void verifyInfoBarClosed() { + Assert.assertNull(mInfo); + } + } + + private static DownloadItem createDownloadItem(OfflineItem offlineItem) { + DownloadInfo downloadInfo = DownloadInfo.fromOfflineItem(offlineItem, null); + return new DownloadItem(false, downloadInfo); + } + + private static OfflineItem createOfflineItem(@OfflineItemState int state) { + OfflineItem item = new OfflineItem(); + String uuid = UUID.randomUUID().toString(); + item.id = LegacyHelpers.buildLegacyContentId(true, uuid); + item.state = state; + if (item.state == OfflineItemState.COMPLETE) { + markItemComplete(item); + } + return item; + } + + private static void markItemComplete(OfflineItem item) { + item.state = OfflineItemState.COMPLETE; + item.title = TEST_FILE_NAME; + item.url = "https://example.com"; + item.receivedBytes = 10L; + item.totalSizeBytes = 10L; + } + + private void waitForMessage(String message) { + CriteriaHelper.pollInstrumentationThread(() -> { + Criteria.checkThat(mTestController.mInfo, Matchers.notNullValue()); + Criteria.checkThat(mTestController.mInfo.message, Matchers.is(message)); + }); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testOfflinePageDownloadStarted() { + mTestController.onDownloadStarted(); + mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING); + } + + @Test + @SmallTest + @Feature({"Download"}) + @Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER) + public void testMultipleDownloadInProgress() { + OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS); + mTestController.onDownloadItemUpdated(createDownloadItem(item1)); + mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING); + + OfflineItem item2 = createOfflineItem(OfflineItemState.IN_PROGRESS); + mTestController.onDownloadItemUpdated(createDownloadItem(item2)); + mTestController.verify(MESSAGE_DOWNLOADING_TWO_FILES, DESCRIPTION_DOWNLOADING); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testSingleOfflineItemComplete() { + OfflineItem item = createOfflineItem(OfflineItemState.IN_PROGRESS); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING); + + markItemComplete(item); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testMultipleOfflineItemComplete() { + OfflineItem item = createOfflineItem(OfflineItemState.IN_PROGRESS); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING); + + markItemComplete(item); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE); + + OfflineItem item2 = createOfflineItem(OfflineItemState.COMPLETE); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_TWO_DOWNLOAD_COMPLETE, null); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testSingleOfflineItemFailed() { + OfflineItem item = createOfflineItem(OfflineItemState.FAILED); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_DOWNLOAD_FAILED, null); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testMultipleOfflineItemFailed() { + OfflineItem item = createOfflineItem(OfflineItemState.FAILED); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_DOWNLOAD_FAILED, null); + + OfflineItem item2 = createOfflineItem(OfflineItemState.FAILED); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_TWO_DOWNLOAD_FAILED, null); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testSingleOfflineItemPending() { + OfflineItem item = createOfflineItem(OfflineItemState.PENDING); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testMultipleOfflineItemPending() { + OfflineItem item = createOfflineItem(OfflineItemState.PENDING); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null); + + OfflineItem item2 = createOfflineItem(OfflineItemState.PENDING); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_TWO_DOWNLOAD_PENDING, null); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testSingleOfflineItemScheduled() { + OfflineItem item = createOfflineItem(OfflineItemState.PENDING); + item.schedule = new OfflineItemSchedule(true, 0); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_DOWNLOAD_SCHEDULED_WIFI, DESCRIPTION_DOWNLOAD_SCHEDULED); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testMultipleOfflineItemScheduled() { + OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS); + item1.schedule = new OfflineItemSchedule(true, 0); + mTestController.onItemUpdated(item1); + mTestController.verify(MESSAGE_DOWNLOAD_SCHEDULED_WIFI, DESCRIPTION_DOWNLOAD_SCHEDULED); + + OfflineItem item2 = createOfflineItem(OfflineItemState.PENDING); + item2.schedule = new OfflineItemSchedule(true, 0); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_TWO_DOWNLOAD_SCHEDULED, null); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testNewDownloadShowsUpImmediately() { + OfflineItem item1 = createOfflineItem(OfflineItemState.COMPLETE); + mTestController.onItemUpdated(item1); + mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE); + + OfflineItem item2 = createOfflineItem(OfflineItemState.IN_PROGRESS); + item2.isAccelerated = true; + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testPausedDownloadsAreIgnored() { + OfflineItem item = createOfflineItem(OfflineItemState.IN_PROGRESS); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING); + + item.state = OfflineItemState.PAUSED; + mTestController.onItemUpdated(item); + mTestController.verifyInfoBarClosed(); + + item.state = OfflineItemState.IN_PROGRESS; + mTestController.onItemUpdated(item); + mTestController.verifyInfoBarClosed(); + + markItemComplete(item); + mTestController.onItemUpdated(item); + mTestController.verifyInfoBarClosed(); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testOnItemRemoved() { + OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS); + OfflineItem item2 = createOfflineItem(OfflineItemState.IN_PROGRESS); + mTestController.onItemUpdated(item1); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_DOWNLOADING_TWO_FILES, DESCRIPTION_DOWNLOADING); + + mTestController.onItemRemoved(item1.id); + mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING); + + mTestController.onItemRemoved(item2.id); + mTestController.verifyInfoBarClosed(); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testCancelledItemWillCloseInfoBar() { + OfflineItem item = createOfflineItem(OfflineItemState.PENDING); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null); + + item.state = OfflineItemState.CANCELLED; + mTestController.onItemUpdated(item); + mTestController.verifyInfoBarClosed(); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testCompleteFailedComplete() { + OfflineItem item1 = createOfflineItem(OfflineItemState.COMPLETE); + mTestController.onItemUpdated(item1); + mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE); + + OfflineItem item2 = createOfflineItem(OfflineItemState.FAILED); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE); + + OfflineItem item3 = createOfflineItem(OfflineItemState.COMPLETE); + mTestController.onItemUpdated(item3); + mTestController.verify(MESSAGE_TWO_DOWNLOAD_COMPLETE, null); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testPendingFailedPending() { + OfflineItem item1 = createOfflineItem(OfflineItemState.PENDING); + mTestController.onItemUpdated(item1); + mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null); + + OfflineItem item2 = createOfflineItem(OfflineItemState.FAILED); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null); + + OfflineItem item3 = createOfflineItem(OfflineItemState.PENDING); + mTestController.onItemUpdated(item3); + mTestController.verify(MESSAGE_TWO_DOWNLOAD_PENDING, null); + + waitForMessage(MESSAGE_DOWNLOAD_FAILED); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testProgressCompleteFailedProgress() { + OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS); + mTestController.onItemUpdated(item1); + mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING); + + OfflineItem item2 = createOfflineItem(OfflineItemState.COMPLETE); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE); + + OfflineItem item3 = createOfflineItem(OfflineItemState.FAILED); + mTestController.onItemUpdated(item3); + mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE); + + waitForMessage(MESSAGE_DOWNLOAD_FAILED); + waitForMessage(MESSAGE_DOWNLOADING_FILE); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testCompleteShowsUpImmediately() { + OfflineItem item1 = createOfflineItem(OfflineItemState.FAILED); + mTestController.onItemUpdated(item1); + mTestController.verify(MESSAGE_DOWNLOAD_FAILED, null); + + OfflineItem item2 = createOfflineItem(OfflineItemState.PENDING); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_DOWNLOAD_FAILED, null); + + markItemComplete(item2); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testResumeFromPendingShowsUpImmediately() { + OfflineItem item1 = createOfflineItem(OfflineItemState.PENDING); + mTestController.onItemUpdated(item1); + mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null); + + OfflineItem item2 = createOfflineItem(OfflineItemState.PENDING); + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_TWO_DOWNLOAD_PENDING, null); + + item2.state = OfflineItemState.IN_PROGRESS; + mTestController.onItemUpdated(item2); + mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null); + + // TODO(shaktisahu): Uncomment the below after fixing. + // item1.state = OfflineItemState.IN_PROGRESS; + // mTestController.onItemUpdated(item1); + // mTestController.verify(MESSAGE_DOWNLOADING_TWO_FILES, DESCRIPTION_DOWNLOADING); + } + + @Test + @SmallTest + @Feature({"Download"}) + public void testPausedAfterPendingWillCloseInfoBar() { + OfflineItem item = createOfflineItem(OfflineItemState.PENDING); + mTestController.onItemUpdated(item); + mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null); + + item.state = OfflineItemState.PAUSED; + mTestController.onItemUpdated(item); + mTestController.verifyInfoBarClosed(); + + item.state = OfflineItemState.IN_PROGRESS; + mTestController.onItemUpdated(item); + mTestController.verifyInfoBarClosed(); + } +}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/SystemDownloadNotifierTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/SystemDownloadNotifierTest.java index ee577dc..a3fa05c 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/SystemDownloadNotifierTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/SystemDownloadNotifierTest.java
@@ -25,6 +25,7 @@ import org.chromium.components.offline_items_collection.ContentId; import org.chromium.components.offline_items_collection.OfflineItemSchedule; import org.chromium.components.offline_items_collection.PendingState; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import java.util.UUID; @@ -47,8 +48,11 @@ @Before public void setUp() { - mMockDownloadNotificationService = new MockDownloadNotificationService(); - mSystemDownloadNotifier.setDownloadNotificationService(mMockDownloadNotificationService); + TestThreadUtils.runOnUiThreadBlocking(() -> { + mMockDownloadNotificationService = new MockDownloadNotificationService(); + mSystemDownloadNotifier.setDownloadNotificationService( + mMockDownloadNotificationService); + }); } private DownloadInfo getDownloadInfo(ContentId id) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java index b890384..79378c0 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java
@@ -202,7 +202,8 @@ // |triggerUi| can be invoked by SwipeRefreshHandler on the rendered // page. Make sure this won't crash after the handler(and also // handler action delegate) is destroyed. - Assert.assertFalse(mNavigationHandler.triggerUi(LEFT_EDGE, 0, 0)); + Assert.assertFalse(TestThreadUtils.runOnUiThreadBlockingNoException( + () -> mNavigationHandler.triggerUi(LEFT_EDGE, 0, 0))); // Just check we're still on the same URL. Assert.assertEquals(mTestServer.getURL(RENDERED_PAGE),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java index 608274f..a4507a5 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java
@@ -86,7 +86,7 @@ () -> mActivityTestRule.getInfoBarContainer() != null); InfoBarContainer container = mActivityTestRule.getInfoBarContainer(); mListener = new InfoBarTestAnimationListener(); - container.addAnimationListener(mListener); + TestThreadUtils.runOnUiThreadBlocking(() -> container.addAnimationListener(mListener)); final String locationUrl = mTestServer.getURL(GEOLOCATION_PAGE); final PermissionInfo geolocationSettings =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java index e9218d6..0faa9bf3 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java
@@ -30,6 +30,7 @@ import org.chromium.components.content_settings.ContentSettingValues; import org.chromium.components.content_settings.ContentSettingsType; import org.chromium.content_public.browser.UiThreadTaskTraits; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.net.test.EmbeddedTestServer; import java.util.concurrent.TimeoutException; @@ -73,8 +74,8 @@ // Infobar should appear when doing the first search. InfoBarContainer container = mActivityTestRule.getInfoBarContainer(); - InfoBarTestAnimationListener listener = new InfoBarTestAnimationListener(); - container.addAnimationListener(listener); + InfoBarTestAnimationListener listener1 = new InfoBarTestAnimationListener(); + TestThreadUtils.runOnUiThreadBlocking(() -> container.addAnimationListener(listener1)); mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE)); // Note: the number of infobars is checked immediately after the URL is loaded, unlike in // other infobar tests where it is checked after animations have completed. This is because @@ -84,7 +85,7 @@ // infobars haven't being shown are invalid. Assert.assertEquals( "Wrong infobar count after search", 1, mActivityTestRule.getInfoBars().size()); - listener.addInfoBarAnimationFinished("InfoBar not added."); + listener1.addInfoBarAnimationFinished("InfoBar not added."); // Infobar should not appear again on the same day. mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE)); @@ -96,12 +97,12 @@ // Infobar should appear again the next day. SearchGeolocationDisclosureTabHelper.setDayOffsetForTesting(1); - listener = new InfoBarTestAnimationListener(); - container.addAnimationListener(listener); + InfoBarTestAnimationListener listener2 = new InfoBarTestAnimationListener(); + TestThreadUtils.runOnUiThreadBlocking(() -> container.addAnimationListener(listener2)); mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE)); Assert.assertEquals( "Wrong infobar count after search", 1, mActivityTestRule.getInfoBars().size()); - listener.addInfoBarAnimationFinished("InfoBar not added."); + listener2.addInfoBarAnimationFinished("InfoBar not added."); // Infobar should not appear again on the same day. mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE)); @@ -111,12 +112,12 @@ // Infobar should appear again the next day. SearchGeolocationDisclosureTabHelper.setDayOffsetForTesting(2); - listener = new InfoBarTestAnimationListener(); - container.addAnimationListener(listener); + InfoBarTestAnimationListener listener3 = new InfoBarTestAnimationListener(); + TestThreadUtils.runOnUiThreadBlocking(() -> container.addAnimationListener(listener3)); mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE)); Assert.assertEquals( "Wrong infobar count after search", 1, mActivityTestRule.getInfoBars().size()); - listener.addInfoBarAnimationFinished("InfoBar not added."); + listener3.addInfoBarAnimationFinished("InfoBar not added."); // Infobar should not appear again on the same day. mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE)); @@ -150,7 +151,7 @@ // Infobar should appear when doing the first search. InfoBarContainer container = mActivityTestRule.getInfoBarContainer(); InfoBarTestAnimationListener listener = new InfoBarTestAnimationListener(); - container.addAnimationListener(listener); + TestThreadUtils.runOnUiThreadBlocking(() -> container.addAnimationListener(listener)); mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE)); Assert.assertEquals( "Wrong infobar count after search", 1, mActivityTestRule.getInfoBars().size());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java index bd0feed4..3a52c853 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java
@@ -144,7 +144,9 @@ new PermissionTestRule.PermissionUpdateWaiter( "denied: ", mNotificationTestRule.getActivity()); - mNotificationTestRule.getActivity().getActivityTab().addObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> { + mNotificationTestRule.getActivity().getActivityTab().addObserver(updateWaiter); + }); mPermissionTestRule.runDenyTest(updateWaiter, NOTIFICATION_TEST_PAGE, "Notification.requestPermission(addCountAndSendToTest)", 1, false, true); @@ -182,7 +184,9 @@ new PermissionTestRule.PermissionUpdateWaiter( "granted: ", mNotificationTestRule.getActivity()); - mNotificationTestRule.getActivity().getActivityTab().addObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> { + mNotificationTestRule.getActivity().getActivityTab().addObserver(updateWaiter); + }); mPermissionTestRule.runAllowTest(updateWaiter, NOTIFICATION_TEST_PAGE, "Notification.requestPermission(addCountAndSendToTest)", 1, false, true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java index 2a4d4228..c0b2e5f 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
@@ -410,12 +410,14 @@ OmniboxTestUtils.waitForOmniboxSuggestions(locationBar); final CallbackHelper loadedCallback = new CallbackHelper(); - mTab.addObserver(new EmptyTabObserver() { - @Override - public void onPageLoadFinished(Tab tab, GURL url) { - loadedCallback.notifyCalled(); - tab.removeObserver(this); - } + TestThreadUtils.runOnUiThreadBlocking(() -> { + mTab.addObserver(new EmptyTabObserver() { + @Override + public void onPageLoadFinished(Tab tab, GURL url) { + loadedCallback.notifyCalled(); + tab.removeObserver(this); + } + }); }); final View v = urlBar;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java index da192d5..a9cc351 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
@@ -226,7 +226,8 @@ onSSLStateUpdatedCallbackHelper.notifyCalled(); } }; - mActivityTestRule.getActivity().getActivityTab().addObserver(observer); + TestThreadUtils.runOnUiThreadBlocking( + () -> mActivityTestRule.getActivity().getActivityTab().addObserver(observer)); try { final String testHttpsUrl = @@ -278,7 +279,8 @@ onSSLStateUpdatedCallbackHelper.notifyCalled(); } }; - mActivityTestRule.getActivity().getActivityTab().addObserver(observer); + TestThreadUtils.runOnUiThreadBlocking( + () -> mActivityTestRule.getActivity().getActivityTab().addObserver(observer)); try { final String testHttpsUrl =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java index aba475e..99bebea2 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java
@@ -108,9 +108,8 @@ @MediumTest @Feature({"RenderTest"}) public void testStatusViewIncognitoWithIcon() throws IOException { - mLocationBarModel.setTab(null, /* incognito= */ true); - runOnUiThreadBlocking(() -> { + mLocationBarModel.setTab(null, /* incognito= */ true); mStatusView.setIncognitoBadgeVisibility(true); mStatusModel.set(StatusProperties.STATUS_ICON_RESOURCE, new StatusIconResource(R.drawable.ic_search, 0)); @@ -122,9 +121,8 @@ @MediumTest @Feature({"RenderTest"}) public void testStatusViewIncognitoNoIcon() throws IOException { - mLocationBarModel.setTab(null, /* incognito= */ true); - runOnUiThreadBlocking(() -> { + mLocationBarModel.setTab(null, /* incognito= */ true); mStatusView.setIncognitoBadgeVisibility(true); mStatusModel.set(StatusProperties.STATUS_ICON_RESOURCE, null); });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java index 5a9d154c..3fb9e2e 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java
@@ -87,11 +87,7 @@ mBottomSheetController = cta.getRootUiCoordinatorForTesting().getBottomSheetController(); mBottomSheetTestSupport = new BottomSheetTestSupport(mBottomSheetController); - mAssistantVoiceSearchConsentUi = new AssistantVoiceSearchConsentUi( - cta.getWindowAndroid(), cta, mSharedPreferencesManager, - () - -> AutofillAssistantPreferenceFragment.launchSettings(cta), - mBottomSheetController); + mAssistantVoiceSearchConsentUi = createConsentUi(); }); } @@ -107,6 +103,17 @@ }); } + private AssistantVoiceSearchConsentUi createConsentUi() { + return TestThreadUtils.runOnUiThreadBlockingNoException(() -> { + ChromeTabbedActivity cta = mActivityTestRule.getActivity(); + return new AssistantVoiceSearchConsentUi(cta.getWindowAndroid(), cta, + mSharedPreferencesManager, + () + -> AutofillAssistantPreferenceFragment.launchSettings(cta), + mBottomSheetController); + }); + } + @Test @MediumTest public void testNoBottomSheetControllerAvailable() { @@ -258,12 +265,7 @@ // Successful showing of the consent calls destroy(). Need to recreate the new // instance to set up the state again. - mAssistantVoiceSearchConsentUi = new AssistantVoiceSearchConsentUi( - mActivityTestRule.getActivity().getWindowAndroid(), mActivityTestRule.getActivity(), - mSharedPreferencesManager, () -> { - AutofillAssistantPreferenceFragment.launchSettings( - mActivityTestRule.getActivity()); - }, mBottomSheetController); + mAssistantVoiceSearchConsentUi = createConsentUi(); verifyAcceptingConsent(); } @@ -282,12 +284,7 @@ ConsentOutcome.CANCELED_VIA_BACK_BUTTON_PRESS); // Successful showing of the consent calls destroy(). Need to recreate the new // instance to set up the state again. - mAssistantVoiceSearchConsentUi = new AssistantVoiceSearchConsentUi( - mActivityTestRule.getActivity().getWindowAndroid(), - mActivityTestRule.getActivity(), mSharedPreferencesManager, () -> { - AutofillAssistantPreferenceFragment.launchSettings( - mActivityTestRule.getActivity()); - }, mBottomSheetController); + mAssistantVoiceSearchConsentUi = createConsentUi(); } // But the max_taps_ignored+1-th will be treated as a rejection.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java index 1d5260b..bf99001 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java
@@ -160,17 +160,14 @@ public void testCloseAllTabs() { final CallbackHelper tabClosed = new CallbackHelper(); final TabModel tabModel = mActivityTestRule.getActivity().getCurrentTabModel(); - mActivityTestRule.getActivity().getCurrentTabModel().addObserver(new TabModelObserver() { - @Override - public void didCloseTab(int tabId, boolean incognito) { - if (tabModel.getCount() == 0) tabClosed.notifyCalled(); - } - }); - InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - mActivityTestRule.getActivity().getTabModelSelector().closeAllTabs(); - } + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + tabModel.addObserver(new TabModelObserver() { + @Override + public void didCloseTab(int tabId, boolean incognito) { + if (tabModel.getCount() == 0) tabClosed.notifyCalled(); + } + }); + mActivityTestRule.getActivity().getTabModelSelector().closeAllTabs(); }); try {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/GeolocationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/GeolocationTest.java index 549318e..e5e2fc1 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/GeolocationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/GeolocationTest.java
@@ -18,6 +18,7 @@ import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.util.browser.LocationSettingsTestUtil; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.device.geolocation.LocationProviderOverrider; import org.chromium.device.geolocation.MockLocationProvider; @@ -52,10 +53,10 @@ Tab tab = mPermissionRule.getActivity().getActivityTab(); PermissionUpdateWaiter updateWaiter = new PermissionUpdateWaiter("Count:", mPermissionRule.getActivity()); - tab.addObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(updateWaiter)); mPermissionRule.runAllowTest( updateWaiter, TEST_FILE, javascript, nUpdates, withGesture, isDialog); - tab.removeObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(updateWaiter)); } /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MIDITest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MIDITest.java index 1b0ad81..ee23c258b 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MIDITest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MIDITest.java
@@ -17,6 +17,7 @@ import org.chromium.chrome.browser.permissions.PermissionTestRule.PermissionUpdateWaiter; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.content_public.browser.test.util.TestThreadUtils; /** * Test suite for MIDI permissions requests. @@ -41,8 +42,8 @@ Tab tab = mPermissionRule.getActivity().getActivityTab(); PermissionUpdateWaiter updateWaiter = new PermissionUpdateWaiter("pass", mPermissionRule.getActivity()); - tab.addObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(updateWaiter)); mPermissionRule.runAllowTest(updateWaiter, TEST_FILE, "", 0, false, true); - tab.removeObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(updateWaiter)); } }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MediaTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MediaTest.java index ef4ed884..f05bef5 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MediaTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MediaTest.java
@@ -17,6 +17,7 @@ import org.chromium.chrome.browser.permissions.PermissionTestRule.PermissionUpdateWaiter; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.common.ContentSwitches; /** @@ -43,10 +44,10 @@ Tab tab = mPermissionRule.getActivity().getActivityTab(); PermissionUpdateWaiter updateWaiter = new PermissionUpdateWaiter(prefix, mPermissionRule.getActivity()); - tab.addObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(updateWaiter)); mPermissionRule.runAllowTest( updateWaiter, TEST_FILE, script, numUpdates, withGesture, isDialog); - tab.removeObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(updateWaiter)); } /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/NotificationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/NotificationTest.java index 9a344b4..6617214 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/NotificationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/NotificationTest.java
@@ -17,6 +17,7 @@ import org.chromium.chrome.browser.permissions.PermissionTestRule.PermissionUpdateWaiter; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.content_public.browser.test.util.TestThreadUtils; /** * Test suite for notifications permissions requests. @@ -42,9 +43,9 @@ Tab tab = mPermissionRule.getActivity().getActivityTab(); PermissionUpdateWaiter updateWaiter = new PermissionUpdateWaiter( "request-callback-granted", mPermissionRule.getActivity()); - tab.addObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(updateWaiter)); mPermissionRule.runAllowTest( updateWaiter, TEST_FILE, "requestPermission()", 0, false, true); - tab.removeObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(updateWaiter)); } }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java index 8eb8f09..3ad8f1a 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java
@@ -20,6 +20,7 @@ import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.util.browser.LocationSettingsTestUtil; import org.chromium.content_public.browser.NavigationHandle; +import org.chromium.content_public.browser.test.util.TestThreadUtils; /** * Test suite for interaction between permissions requests and navigation. @@ -68,9 +69,9 @@ callbackHelper.notifyCalled(); } }; - tab.addObserver(navigationWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(navigationWaiter)); callbackHelper.waitForCallback(0); - tab.removeObserver(navigationWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(navigationWaiter)); mPermissionRule.waitForDialogShownState(false); }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/QuotaTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/QuotaTest.java index 48fb406..0f8c53c1c 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/QuotaTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/QuotaTest.java
@@ -17,6 +17,7 @@ import org.chromium.chrome.browser.permissions.PermissionTestRule.PermissionUpdateWaiter; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.content_public.browser.test.util.TestThreadUtils; /** * Test suite for quota permissions requests. @@ -41,10 +42,10 @@ Tab tab = mPermissionRule.getActivity().getActivityTab(); PermissionUpdateWaiter updateWaiter = new PermissionUpdateWaiter("Count: ", mPermissionRule.getActivity()); - tab.addObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(updateWaiter)); mPermissionRule.runAllowTest( updateWaiter, TEST_FILE, script, numUpdates, withGesture, isDialog); - tab.removeObserver(updateWaiter); + TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(updateWaiter)); } /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/reengagement/ReengagementNotificationControllerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/reengagement/ReengagementNotificationControllerIntegrationTest.java index 567c44d..fa0aef4 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/reengagement/ReengagementNotificationControllerIntegrationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/reengagement/ReengagementNotificationControllerIntegrationTest.java
@@ -283,7 +283,10 @@ tabAddedCallback.notifyCalled(); } }; - mTabbedActivityTestRule.getActivity().getTabModelSelector().addObserver(selectorObserver); + TestThreadUtils.runOnUiThreadBlocking(() -> { + mTabbedActivityTestRule.getActivity().getTabModelSelector().addObserver( + selectorObserver); + }); Intent intent = new Intent(InstrumentationRegistry.getTargetContext(), ReengagementActivity.class);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java index ab3390d..758204e 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -510,7 +510,7 @@ Assert.assertEquals(0, mTestDelegate.onFinishDeferredInitializationCallback.getCallCount()); // Dismiss the dialog without acting on it. - mTestDelegate.shownPromoDialog.dismiss(); + TestThreadUtils.runOnUiThreadBlocking(() -> mTestDelegate.shownPromoDialog.dismiss()); // SearchActivity should realize the failure case and prevent the user from using it. CriteriaHelper.pollUiThread(() -> {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PriceDropMetricsLoggerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PriceDropMetricsLoggerTest.java index f37585c..4267faf 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PriceDropMetricsLoggerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PriceDropMetricsLoggerTest.java
@@ -22,6 +22,7 @@ import org.chromium.base.test.util.CommandLineFlags; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.test.ChromeBrowserTestRule; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import java.util.concurrent.TimeUnit; @@ -42,7 +43,7 @@ @Before public void setUp() { - MockitoAnnotations.initMocks(this); + TestThreadUtils.runOnUiThreadBlocking(() -> MockitoAnnotations.initMocks(this)); doReturn("offer-id").when(mShoppingPersistedTabData).getMainOfferId(); doReturn(true).when(mShoppingPersistedTabData).hasPriceMicros(); doReturn(true).when(mShoppingPersistedTabData).hasPreviousPriceMicros();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java index 3d4b4c9..0d8e502b 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java
@@ -22,6 +22,7 @@ import org.chromium.chrome.browser.tab.TabObserver; import org.chromium.chrome.browser.tab.TabTestUtils; import org.chromium.content_public.browser.LoadUrlParams; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import java.util.HashSet; import java.util.Set; @@ -109,29 +110,32 @@ @Test @SmallTest public void testObserverAddedBeforeInitialize() { - TabModelSelectorBase selector = new TabModelSelectorBase( - null, EmptyTabModelFilter::new, false) { - @Override - public void requestToShowTab(Tab tab, int type) {} + TabModelSelectorBase selector = TestThreadUtils.runOnUiThreadBlockingNoException(() -> { + return new TabModelSelectorBase(null, EmptyTabModelFilter::new, false) { + @Override + public void requestToShowTab(Tab tab, int type) {} - @Override - public boolean closeAllTabsRequest(boolean incognito) { - return false; - } + @Override + public boolean closeAllTabsRequest(boolean incognito) { + return false; + } - @Override - public boolean isSessionRestoreInProgress() { - return false; - } + @Override + public boolean isSessionRestoreInProgress() { + return false; + } - @Override - public Tab openNewTab(LoadUrlParams loadUrlParams, @TabLaunchType int type, Tab parent, - boolean incognito) { - return null; - } - }; + @Override + public Tab openNewTab(LoadUrlParams loadUrlParams, @TabLaunchType int type, + Tab parent, boolean incognito) { + return null; + } + }; + }); TestTabModelSelectorTabObserver observer = createTabModelSelectorTabObserver(); - selector.initialize(sTestRule.getNormalTabModel(), sTestRule.getIncognitoTabModel()); + TestThreadUtils.runOnUiThreadBlocking(() -> { + selector.initialize(sTestRule.getNormalTabModel(), sTestRule.getIncognitoTabModel()); + }); Tab normalTab1 = createTestTab(false); addTab(sTestRule.getNormalTabModel(), normalTab1);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java index 5e6baa6..1330587 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java
@@ -88,6 +88,9 @@ private TabbedModeTabModelOrchestrator buildTestTabModelSelector( int[] normalTabIds, int[] incognitoTabIds) throws Exception { + final CallbackHelper callbackSignal = new CallbackHelper(); + final int callCount = callbackSignal.getCallCount(); + MockTabModel.MockTabModelDelegate tabModelDelegate = new MockTabModel.MockTabModelDelegate() { @Override @@ -101,21 +104,29 @@ return tab; } }; - final MockTabModel normalTabModel = new MockTabModel(false, tabModelDelegate); - final MockTabModel incognitoTabModel = new MockTabModel(true, tabModelDelegate); - TabbedModeTabModelOrchestrator orchestrator = new TabbedModeTabModelOrchestrator(false); - TestThreadUtils.runOnUiThreadBlocking( - () -> orchestrator.createTabModels(new ChromeTabbedActivity(), null, null, 0)); - TabModelSelector selector = orchestrator.getTabModelSelector(); - ((MockTabModelSelector) selector).initializeTabModels(normalTabModel, incognitoTabModel); - final CallbackHelper callbackSignal = new CallbackHelper(); - final int callCount = callbackSignal.getCallCount(); - TabPersistentStore store = orchestrator.getTabPersistentStoreForTesting(); - store.addObserver(new TabPersistentStoreObserver() { - @Override - public void onMetadataSavedAsynchronously(TabModelSelectorMetadata metadata) { - callbackSignal.notifyCalled(); - } + + final MockTabModel normalTabModel = TestThreadUtils.runOnUiThreadBlocking( + () -> new MockTabModel(false, tabModelDelegate)); + final MockTabModel incognitoTabModel = TestThreadUtils.runOnUiThreadBlocking( + () -> new MockTabModel(true, tabModelDelegate)); + TabbedModeTabModelOrchestrator orchestrator = TestThreadUtils.runOnUiThreadBlocking(() -> { + TabbedModeTabModelOrchestrator tmpOrchestrator = + new TabbedModeTabModelOrchestrator(false); + tmpOrchestrator.createTabModels(new ChromeTabbedActivity(), null, null, 0); + TabModelSelector selector = tmpOrchestrator.getTabModelSelector(); + ((MockTabModelSelector) selector) + .initializeTabModels(normalTabModel, incognitoTabModel); + return tmpOrchestrator; + }); + TabPersistentStore store = TestThreadUtils.runOnUiThreadBlocking(() -> { + TabPersistentStore tmpStore = orchestrator.getTabPersistentStoreForTesting(); + tmpStore.addObserver(new TabPersistentStoreObserver() { + @Override + public void onMetadataSavedAsynchronously(TabModelSelectorMetadata metadata) { + callbackSignal.notifyCalled(); + } + }); + return tmpStore; }); // Adding tabs results in writing to disk running on AsyncTasks. Run on the main thread
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarIntegrationTest.java index ae7a2fb..8ac15c2bd 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarIntegrationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarIntegrationTest.java
@@ -68,7 +68,8 @@ final WebContents webContents = mActivityTestRule.getWebContents(); - TestWebContentsObserver observer = new TestWebContentsObserver(webContents); + TestWebContentsObserver observer = TestThreadUtils.runOnUiThreadBlockingNoException( + () -> new TestWebContentsObserver(webContents)); // Start and stop load events are carefully tracked; there should be two start-stop pairs // that do not overlap. OnPageStartedHelper startHelper = observer.getOnPageStartedHelper();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerTest.java index e8aad005..35b9b5c 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerTest.java
@@ -164,9 +164,11 @@ public void onDismiss(PropertyModel model, int dismissalCause) {} }; - PropertyModel dialogModel = (new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS) - .with(ModalDialogProperties.CONTROLLER, controller) - .build()); + PropertyModel dialogModel = TestThreadUtils.runOnUiThreadBlockingNoException(() -> { + return new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS) + .with(ModalDialogProperties.CONTROLLER, controller) + .build(); + }); TestThreadUtils.runOnUiThreadBlocking(() -> { sActivityTestRule.getActivity().getModalDialogManager().showDialog(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateCompactInfoBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateCompactInfoBarTest.java index 864076b..7238bbd 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateCompactInfoBarTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateCompactInfoBarTest.java
@@ -54,16 +54,18 @@ @Before public void setUp() throws Exception { mActivityTestRule.startMainActivityOnBlankPage(); - TestThreadUtils.runOnUiThreadBlocking( - () -> TranslateBridge.setIgnoreMissingKeyForTesting(true)); - mInfoBarContainer = mActivityTestRule.getInfoBarContainer(); - mListener = new InfoBarTestAnimationListener(); - mInfoBarContainer.addAnimationListener(mListener); + TestThreadUtils.runOnUiThreadBlocking(() -> { + TranslateBridge.setIgnoreMissingKeyForTesting(true); + mInfoBarContainer = mActivityTestRule.getInfoBarContainer(); + mListener = new InfoBarTestAnimationListener(); + mInfoBarContainer.addAnimationListener(mListener); + }); } @After public void tearDown() { - mInfoBarContainer.removeAnimationListener(mListener); + TestThreadUtils.runOnUiThreadBlocking( + () -> mInfoBarContainer.removeAnimationListener(mListener)); } /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/video/FullscreenVideoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/video/FullscreenVideoTest.java index 2aefb5ae..8556045 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/video/FullscreenVideoTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/video/FullscreenVideoTest.java
@@ -31,6 +31,7 @@ import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.test.util.DOMUtils; import org.chromium.content_public.browser.test.util.KeyUtils; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestTouchUtils; import org.chromium.media.MediaSwitches; import org.chromium.net.test.EmbeddedTestServerRule; @@ -84,7 +85,8 @@ mActivityTestRule.loadUrl(url); Tab tab = mActivity.getActivityTab(); FullscreenManager.Observer listener = new FullscreenToggleListener(); - mActivity.getFullscreenManager().addObserver(listener); + TestThreadUtils.runOnUiThreadBlocking( + () -> mActivity.getFullscreenManager().addObserver(listener)); TestTouchUtils.singleClickView( InstrumentationRegistry.getInstrumentation(), tab.getView(), 500, 500); @@ -113,7 +115,8 @@ final Tab tab = mActivity.getActivityTab(); FullscreenManager.Observer listener = new FullscreenToggleListener(); - mActivity.getFullscreenManager().addObserver(listener); + TestThreadUtils.runOnUiThreadBlocking( + () -> mActivity.getFullscreenManager().addObserver(listener)); // Start playback to guarantee it's properly loaded. WebContents webContents = mActivity.getCurrentWebContents();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java index 8c4c9f3..940a047f 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java
@@ -40,6 +40,7 @@ import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport; import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver; import org.chromium.components.browser_ui.bottomsheet.TestBottomSheetContent; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.ui.test.util.UiRestriction; import java.util.concurrent.ExecutionException; @@ -151,7 +152,9 @@ public void testTabObscuringState() throws TimeoutException { CallbackHelper obscuringStateChangedHelper = new CallbackHelper(); TabObscuringHandler handler = mTestRule.getActivity().getTabObscuringHandler(); - handler.addObserver((isObscured) -> obscuringStateChangedHelper.notifyCalled()); + TestThreadUtils.runOnUiThreadBlocking(() -> { + handler.addObserver((isObscured) -> obscuringStateChangedHelper.notifyCalled()); + }); mHighPriorityContent.setHasCustomScrimLifecycle(false); assertFalse("The tab should not yet be obscured.", handler.areAllTabsObscured()); @@ -173,7 +176,9 @@ public void testTabObscuringState_customScrim() throws ExecutionException { CallbackHelper obscuringStateChangedHelper = new CallbackHelper(); TabObscuringHandler handler = mTestRule.getActivity().getTabObscuringHandler(); - handler.addObserver((isObscured) -> obscuringStateChangedHelper.notifyCalled()); + TestThreadUtils.runOnUiThreadBlocking(() -> { + handler.addObserver((isObscured) -> obscuringStateChangedHelper.notifyCalled()); + }); mHighPriorityContent.setHasCustomScrimLifecycle(true); assertFalse("The tab should not be obscured.", handler.areAllTabsObscured());
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index b26d2b08..e772e37d 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -786,6 +786,8 @@ "metrics/metrics_memory_details.h", "metrics/metrics_reporting_state.cc", "metrics/metrics_reporting_state.h", + "metrics/metrics_services_web_contents_observer.cc", + "metrics/metrics_services_web_contents_observer.h", "metrics/network_quality_estimator_provider_impl.cc", "metrics/network_quality_estimator_provider_impl.h", "metrics/oom/out_of_memory_reporter.cc", @@ -2390,6 +2392,7 @@ "//chrome/browser/ash/system_extensions", "//chrome/browser/nearby_sharing/common", "//chrome/browser/ui/webui/chromeos/add_supervision:mojo_bindings", + "//chrome/browser/ui/webui/chromeos/audio:mojo_bindings", "//chrome/browser/ui/webui/chromeos/crostini_installer:mojo_bindings", "//chrome/browser/ui/webui/chromeos/crostini_upgrader:mojo_bindings", "//chrome/browser/ui/webui/chromeos/emoji:mojo_bindings", @@ -4063,6 +4066,7 @@ "sharesheet/sharesheet_service_delegate.h", "sharesheet/sharesheet_service_factory.cc", "sharesheet/sharesheet_service_factory.h", + "sharesheet/sharesheet_ui_delegate.h", "sharing/click_to_call/click_to_call_context_menu_observer.cc", "sharing/click_to_call/click_to_call_context_menu_observer.h", "sharing/click_to_call/click_to_call_metrics.cc",
diff --git a/chrome/browser/ash/arc/wallpaper/arc_wallpaper_service.cc b/chrome/browser/ash/arc/wallpaper/arc_wallpaper_service.cc index ca3f656..f4ef505 100644 --- a/chrome/browser/ash/arc/wallpaper/arc_wallpaper_service.cc +++ b/chrome/browser/ash/arc/wallpaper/arc_wallpaper_service.cc
@@ -169,12 +169,10 @@ int32_t android_id) { const AccountId account_id = UserManager::Get()->GetPrimaryUser()->GetAccountId(); - const std::string wallpaper_files_id = - WallpaperControllerClientImpl::Get()->GetFilesId(account_id); const bool result = WallpaperControllerClientImpl::Get()->SetThirdPartyWallpaper( - account_id, wallpaper_files_id, kAndroidWallpaperFilename, + account_id, kAndroidWallpaperFilename, ash::WALLPAPER_LAYOUT_CENTER_CROPPED, image); // Notify the Android side whether the request is going through or not.
diff --git a/chrome/browser/ash/full_restore/full_restore_app_launch_handler.cc b/chrome/browser/ash/full_restore/full_restore_app_launch_handler.cc index d6244af..c9ea900 100644 --- a/chrome/browser/ash/full_restore/full_restore_app_launch_handler.cc +++ b/chrome/browser/ash/full_restore/full_restore_app_launch_handler.cc
@@ -160,8 +160,6 @@ // active profile path is set when switch users. ::full_restore::SetActiveProfilePath(profile_->GetPath()); } - - MaybePostRestore(); } void FullRestoreAppLaunchHandler::MaybePostRestore() {
diff --git a/chrome/browser/ash/full_restore/full_restore_app_launch_handler_browsertest.cc b/chrome/browser/ash/full_restore/full_restore_app_launch_handler_browsertest.cc index 429072ba..53c065f 100644 --- a/chrome/browser/ash/full_restore/full_restore_app_launch_handler_browsertest.cc +++ b/chrome/browser/ash/full_restore/full_restore_app_launch_handler_browsertest.cc
@@ -303,6 +303,12 @@ } ~FullRestoreAppLaunchHandlerBrowserTest() override = default; + void SetShouldRestore(FullRestoreAppLaunchHandler* app_launch_handler) { + content::RunAllTasksUntilIdle(); + app_launch_handler->SetShouldRestore(); + content::RunAllTasksUntilIdle(); + } + void CreateWebApp() { auto web_application_info = std::make_unique<WebApplicationInfo>(); web_application_info->start_url = GURL("https://example.org"); @@ -381,9 +387,7 @@ // Create FullRestoreAppLaunchHandler, and set should restore. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - app_launch_handler->SetShouldRestore(); - - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); // Verify there is no new browser launched. EXPECT_EQ(count, BrowserList::GetInstance()->size()); @@ -405,7 +409,7 @@ // Create FullRestoreAppLaunchHandler, and set should restore. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - app_launch_handler->SetShouldRestore(); + SetShouldRestore(app_launch_handler.get()); CreateWebApp(); @@ -437,7 +441,7 @@ // Create FullRestoreAppLaunchHandler, and set should restore. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - app_launch_handler->SetShouldRestore(); + SetShouldRestore(app_launch_handler.get()); // The web app window should be attainable. CreateWebApp(); @@ -475,10 +479,7 @@ CreateWebApp(); - // Set should restore. - app_launch_handler->SetShouldRestore(); - - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); EXPECT_TRUE(FindWebAppWindow()); } @@ -556,10 +557,7 @@ // Create FullRestoreAppLaunchHandler. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - - // Set should restore. - app_launch_handler->SetShouldRestore(); - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false); content::RunAllTasksUntilIdle(); @@ -590,10 +588,7 @@ // Create FullRestoreAppLaunchHandler. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - - // Set should restore. - app_launch_handler->SetShouldRestore(); - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false); content::RunAllTasksUntilIdle(); @@ -658,11 +653,9 @@ // Create FullRestoreAppLaunchHandler, and set should restore. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - app_launch_handler->SetShouldRestore(); - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false); - content::RunAllTasksUntilIdle(); CreateWebApp(); content::RunAllTasksUntilIdle(); @@ -695,14 +688,10 @@ std::make_unique<FullRestoreAppLaunchHandler>(profile()); app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false); - content::RunAllTasksUntilIdle(); CreateWebApp(); - content::RunAllTasksUntilIdle(); - // Set should restore. - app_launch_handler->SetShouldRestore(); - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); // Verify there is new browser launched. EXPECT_EQ(count + 2, BrowserList::GetInstance()->size()); @@ -726,8 +715,7 @@ auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false); - app_launch_handler->SetShouldRestore(); - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); ASSERT_EQ(count + 1u, BrowserList::GetInstance()->size()); @@ -786,8 +774,7 @@ auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false); - app_launch_handler->SetShouldRestore(); - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); ASSERT_EQ(count + 1u, browser_list->size()); ASSERT_EQ(1u, browser_list->size()); @@ -852,8 +839,7 @@ // Read from the restore data. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - app_launch_handler->SetShouldRestore(); - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); // Verify the restore window id. app_window = CreateAppWindow(browser()->profile(), extension); @@ -922,8 +908,7 @@ // Read from the restore data. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - app_launch_handler->SetShouldRestore(); - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); // Tests that the created window is minimized. app_window = CreateAppWindow(browser()->profile(), extension); @@ -959,8 +944,7 @@ // Read from the restore data. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - app_launch_handler->SetShouldRestore(); - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); // Verify the restore window id; app_window1 = CreateAppWindow(browser()->profile(), extension); @@ -1023,8 +1007,7 @@ // Read from the restore data. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - app_launch_handler->SetShouldRestore(); - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); // Tests that the created window is not fullscreen. app_window = CreateAppWindow(browser()->profile(), extension); @@ -1139,9 +1122,7 @@ app_launch_handler_ = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - app_launch_handler_->SetShouldRestore(); - - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler_.get()); } void ForceLaunchApp(const std::string& app_id, int32_t window_id) { @@ -2255,9 +2236,7 @@ // Simulate the user clicks the `restore` button. auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - app_launch_handler->SetShouldRestore(); - - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); widget1->CloseNow(); @@ -2347,6 +2326,12 @@ false /* should_notify_initialized */); } + void SetShouldRestore(FullRestoreAppLaunchHandler* app_launch_handler) { + content::RunAllTasksUntilIdle(); + app_launch_handler->SetShouldRestore(); + content::RunAllTasksUntilIdle(); + } + bool HasWindowInfo(int32_t restore_window_id) { return ::full_restore::FullRestoreReadHandler::GetInstance()->HasWindowInfo( restore_window_id); @@ -2377,11 +2362,8 @@ ASSERT_FALSE(HasWindowInfo(window_id)); - // Set should restore. - app_launch_handler->SetShouldRestore(); + SetShouldRestore(app_launch_handler.get()); - // Wait for the restoration. - content::RunAllTasksUntilIdle(); ASSERT_TRUE(HasWindowInfo(window_id)); // Get the restored browser for the system web app. @@ -2458,11 +2440,7 @@ auto app_launch_handler = std::make_unique<FullRestoreAppLaunchHandler>(profile()); - // Set should restore. - app_launch_handler->SetShouldRestore(); - - // Wait for the restoration. - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); // Verify the app is not restored because the app is not installed. Browser* restore_app_browser = GetBrowserForWindowId(window_id); @@ -2518,13 +2496,7 @@ // Close |app_browser| so that the SWA can be relaunched. web_app::CloseAndWait(app_browser); - // Set should restore. - app_launch_handler->SetShouldRestore(); - - // Wait for the restoration. - // TODO(chinsenj|nancylingwang): Look into using a more specific signal to - // detect when restoration is done. - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); // Get the restored browser for the system web app. Browser* restore_app_browser = GetBrowserForWindowId(window_id); @@ -2584,11 +2556,7 @@ web_app::CloseAndWait(app1_browser); web_app::CloseAndWait(app2_browser); - // Set should restore. - app_launch_handler->SetShouldRestore(); - - // Wait for the restoration. - content::RunAllTasksUntilIdle(); + SetShouldRestore(app_launch_handler.get()); aura::Window* restore_app1_window = nullptr; aura::Window* restore_app2_window = nullptr;
diff --git a/chrome/browser/ash/guest_os/guest_os_registry_service.cc b/chrome/browser/ash/guest_os/guest_os_registry_service.cc index d1c7666..658a90c 100644 --- a/chrome/browser/ash/guest_os/guest_os_registry_service.cc +++ b/chrome/browser/ash/guest_os/guest_os_registry_service.cc
@@ -58,6 +58,9 @@ constexpr char kPluginVmAppsInstalledHistogram[] = "PluginVm.AppsInstalledAtLogin"; +constexpr char kBorealisAppsInstalledHistogram[] = + "Borealis.AppsInstalledAtLogin"; + base::Value ProtoToDictionary(const App::LocaleString& locale_string) { base::Value result(base::Value::Type::DICTIONARY); for (const App::LocaleString::Entry& entry : locale_string.values()) { @@ -354,14 +357,10 @@ GuestOsRegistryService::VmType GuestOsRegistryService::Registration::VmType() const { - absl::optional<int> vm_type = - pref_.FindIntKey(guest_os::prefs::kAppVmTypeKey); // The VmType field is new, existing Apps that do not include it must be - // TERMINA Apps, as Plugin VM apps are not yet in production. - if (!vm_type) { - return GuestOsRegistryService::VmType::ApplicationList_VmType_TERMINA; - } - return static_cast<GuestOsRegistryService::VmType>(*vm_type); + // TERMINA (0) Apps, as Plugin VM apps are not yet in production. + return static_cast<GuestOsRegistryService::VmType>( + pref_.FindIntKey(guest_os::prefs::kAppVmTypeKey).value_or(0)); } std::string GuestOsRegistryService::Registration::VmName() const { @@ -378,8 +377,7 @@ } std::string GuestOsRegistryService::Registration::Name() const { - if (VmType() == - GuestOsRegistryService::VmType::ApplicationList_VmType_PLUGIN_VM) { + if (VmType() == VmType::ApplicationList_VmType_PLUGIN_VM) { return l10n_util::GetStringFUTF8( IDS_PLUGIN_VM_APP_NAME_WINDOWS_SUFFIX, base::UTF8ToUTF16(LocalizedString(guest_os::prefs::kAppNameKey))); @@ -643,18 +641,7 @@ const base::DictionaryValue* apps = prefs_->GetDictionary(guest_os::prefs::kGuestOsRegistry); - bool crostini_enabled = - crostini::CrostiniFeatures::Get()->IsEnabled(profile_); - bool plugin_vm_enabled = - plugin_vm::PluginVmFeatures::Get()->IsEnabled(profile_); - bool borealis_enabled = borealis::BorealisService::GetForProfile(profile_) - ->Features() - .IsEnabled(); - if (!crostini_enabled && !plugin_vm_enabled && !borealis_enabled) - return; - - int num_crostini_apps = 0; - int num_plugin_vm_apps = 0; + base::flat_map<int, int> num_apps; for (const auto item : apps->DictItems()) { if (item.first == crostini::kCrostiniTerminalSystemAppId) @@ -665,28 +652,27 @@ if (no_display && no_display.value()) continue; - absl::optional<int> vm_type = - item.second.FindIntKey(guest_os::prefs::kAppVmTypeKey); - if (!vm_type || - vm_type == - GuestOsRegistryService::VmType::ApplicationList_VmType_TERMINA) { - num_crostini_apps++; - } else if (vm_type == GuestOsRegistryService::VmType:: - ApplicationList_VmType_PLUGIN_VM) { - num_plugin_vm_apps++; - } else { - NOTREACHED(); - } + int vm_type = + item.second.FindIntKey(guest_os::prefs::kAppVmTypeKey).value_or(0); + num_apps[vm_type]++; } - if (crostini_enabled) + if (crostini::CrostiniFeatures::Get()->IsEnabled(profile_)) { UMA_HISTOGRAM_COUNTS_1000(kCrostiniAppsInstalledHistogram, - num_crostini_apps); - if (plugin_vm_enabled) - UMA_HISTOGRAM_COUNTS_1000(kPluginVmAppsInstalledHistogram, - num_plugin_vm_apps); - - // TODO(b/166691285): borealis launch metrics. + num_apps[VmType::ApplicationList_VmType_TERMINA]); + } + if (plugin_vm::PluginVmFeatures::Get()->IsEnabled(profile_)) { + UMA_HISTOGRAM_COUNTS_1000( + kPluginVmAppsInstalledHistogram, + num_apps[VmType::ApplicationList_VmType_PLUGIN_VM]); + } + if (borealis::BorealisService::GetForProfile(profile_) + ->Features() + .IsEnabled()) { + UMA_HISTOGRAM_COUNTS_1000( + kBorealisAppsInstalledHistogram, + num_apps[VmType::ApplicationList_VmType_BOREALIS]); + } } base::FilePath GuestOsRegistryService::GetAppPath(
diff --git a/chrome/browser/ash/sharesheet/OWNERS b/chrome/browser/ash/sharesheet/OWNERS deleted file mode 100644 index d1aabab..0000000 --- a/chrome/browser/ash/sharesheet/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -file://chrome/browser/sharesheet/OWNERS
diff --git a/chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.h b/chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.h deleted file mode 100644 index 6bff6e0..0000000 --- a/chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.h +++ /dev/null
@@ -1,59 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_ASH_SHARESHEET_CROS_SHARESHEET_SERVICE_DELEGATE_H_ -#define CHROME_BROWSER_ASH_SHARESHEET_CROS_SHARESHEET_SERVICE_DELEGATE_H_ - -#include "chrome/browser/sharesheet/sharesheet_controller.h" -#include "chrome/browser/sharesheet/sharesheet_service_delegate.h" -#include "chrome/browser/sharesheet/sharesheet_types.h" -#include "components/services/app_service/public/mojom/types.mojom.h" -#include "ui/gfx/native_widget_types.h" - -namespace ash { -namespace sharesheet { - -class SharesheetBubbleView; - -// The Chrome OS only SharesheetServiceDelegate class. -// CrosSharesheetServiceDelegate is the interface through which the business -// logic in the SharesheetService communicates with the UI -// (SharesheetBubbleView). -class CrosSharesheetServiceDelegate - : public ::sharesheet::SharesheetServiceDelegate { - public: - CrosSharesheetServiceDelegate( - gfx::NativeWindow native_window, - ::sharesheet::SharesheetService* sharesheet_service); - ~CrosSharesheetServiceDelegate() override = default; - CrosSharesheetServiceDelegate(const CrosSharesheetServiceDelegate&) = delete; - CrosSharesheetServiceDelegate& operator=( - const CrosSharesheetServiceDelegate&) = delete; - - // ::sharesheet::SharesheetServiceDelegate: - void ShowBubble(std::vector<::sharesheet::TargetInfo> targets, - apps::mojom::IntentPtr intent, - ::sharesheet::DeliveredCallback delivered_callback, - ::sharesheet::CloseCallback close_callback) override; - void ShowNearbyShareBubbleForArc( - apps::mojom::IntentPtr intent, - ::sharesheet::DeliveredCallback delivered_callback, - ::sharesheet::CloseCallback close_callback) override; - void OnActionLaunched() override; - - // ::sharesheet::SharesheetController: - void SetBubbleSize(int width, int height) override; - void CloseBubble(::sharesheet::SharesheetResult result) override; - - private: - bool IsBubbleVisible() const; - - // Owned by views. - SharesheetBubbleView* sharesheet_bubble_view_; -}; - -} // namespace sharesheet -} // namespace ash - -#endif // CHROME_BROWSER_ASH_SHARESHEET_CROS_SHARESHEET_SERVICE_DELEGATE_H_
diff --git a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc index 2064301..a9afdbc 100644 --- a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc +++ b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
@@ -265,12 +265,11 @@ DCHECK(user); auto* controller = ash::WallpaperController::Get(); - auto* client = WallpaperControllerClientImpl::Get(); const auto& account_id = user->GetAccountId(); controller->SetCustomWallpaper( - account_id, client->GetFilesId(account_id), it->second.path, + account_id, it->second.path, ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED, /*preview_mode=*/false, std::move(callback)); }
diff --git a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc index 6d06b8b..07fd1ac 100644 --- a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc +++ b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc
@@ -78,18 +78,18 @@ base::Owned(pending_hosts))); for (const StorageType& type : types) { - quota_manager_->GetStorageKeysModifiedBetween( - type, base::Time(), base::Time::Max(), - base::BindOnce(&BrowsingDataQuotaHelperImpl::GotStorageKeys, - weak_factory_.GetWeakPtr(), pending_hosts, completion)); + quota_manager_->GetStorageKeysForType( + type, base::BindOnce(&BrowsingDataQuotaHelperImpl::GotStorageKeys, + weak_factory_.GetWeakPtr(), pending_hosts, + completion, type)); } } void BrowsingDataQuotaHelperImpl::GotStorageKeys( PendingHosts* pending_hosts, base::OnceClosure completion, - const std::set<blink::StorageKey>& storage_keys, - StorageType type) { + StorageType type, + const std::set<blink::StorageKey>& storage_keys) { DCHECK_CURRENTLY_ON(BrowserThread::IO); for (const blink::StorageKey& storage_key : storage_keys) { if (!browsing_data::IsWebScheme(storage_key.origin().scheme()))
diff --git a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h index 0fecffc6..6d648c1 100644 --- a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h +++ b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h
@@ -46,11 +46,11 @@ // Calls QuotaManager::GetStorageKeysModifiedBetween for each storage type. void FetchQuotaInfoOnIOThread(FetchResultCallback callback); - // Callback function for QuotaManager::GetStorageKeysModifiedBetween. + // Callback function for QuotaManager::GetStorageKeysForType. void GotStorageKeys(PendingHosts* pending_hosts, base::OnceClosure completion, - const std::set<blink::StorageKey>& storage_keys, - blink::mojom::StorageType type); + blink::mojom::StorageType type, + const std::set<blink::StorageKey>& storage_keys); // Calls QuotaManager::GetHostUsage for each (origin, type) pair. void OnGetOriginsComplete(FetchResultCallback callback,
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper.cc b/chrome/browser/browsing_data/counters/site_data_counting_helper.cc index 0714dd3..590a4bd 100644 --- a/chrome/browser/browsing_data/counters/site_data_counting_helper.cc +++ b/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
@@ -10,6 +10,7 @@ #include "chrome/browser/profiles/profile.h" #include "components/browsing_data/content/browsing_data_helper.h" #include "components/content_settings/core/browser/host_content_settings_map.h" +#include "components/services/storage/public/cpp/buckets/bucket_info.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/dom_storage_context.h" @@ -62,9 +63,9 @@ if (quota_manager) { // Count storage keys with filesystem, websql, appcache, indexeddb, // serviceworkers and cachestorage using quota manager. - auto storage_keys_callback = base::BindRepeating( - &SiteDataCountingHelper::GetQuotaStorageKeysCallback, - base::Unretained(this)); + auto buckets_callback = + base::BindRepeating(&SiteDataCountingHelper::GetQuotaBucketsCallback, + base::Unretained(this)); const blink::mojom::StorageType types[] = { blink::mojom::StorageType::kTemporary, blink::mojom::StorageType::kPersistent, @@ -73,9 +74,8 @@ tasks_ += 1; content::GetIOThreadTaskRunner({})->PostTask( FROM_HERE, - base::BindOnce(&storage::QuotaManager::GetStorageKeysModifiedBetween, - quota_manager, type, begin_, end_, - storage_keys_callback)); + base::BindOnce(&storage::QuotaManager::GetBucketsModifiedBetween, + quota_manager, type, begin_, end_, buckets_callback)); } } @@ -156,17 +156,17 @@ base::Unretained(this), origins)); } -void SiteDataCountingHelper::GetQuotaStorageKeysCallback( - const std::set<blink::StorageKey>& storage_keys, +void SiteDataCountingHelper::GetQuotaBucketsCallback( + const std::set<storage::BucketInfo>& buckets, blink::mojom::StorageType type) { DCHECK_CURRENTLY_ON(BrowserThread::IO); - std::vector<GURL> urls; - urls.resize(storage_keys.size()); - for (const blink::StorageKey& storage_key : storage_keys) - urls.push_back(storage_key.origin().GetURL()); + std::set<GURL> urls; + for (const storage::BucketInfo& bucket : buckets) + urls.insert(bucket.storage_key.origin().GetURL()); content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, base::BindOnce(&SiteDataCountingHelper::Done, - base::Unretained(this), std::move(urls))); + FROM_HERE, + base::BindOnce(&SiteDataCountingHelper::Done, base::Unretained(this), + std::vector<GURL>(urls.begin(), urls.end()))); } void SiteDataCountingHelper::GetLocalStorageUsageInfoCallback(
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper.h b/chrome/browser/browsing_data/counters/site_data_counting_helper.h index 1d5cfd7..3ee94de3 100644 --- a/chrome/browser/browsing_data/counters/site_data_counting_helper.h +++ b/chrome/browser/browsing_data/counters/site_data_counting_helper.h
@@ -19,16 +19,13 @@ class Profile; class HostContentSettingsMap; -namespace blink { -class StorageKey; -} - namespace content { struct StorageUsageInfo; } namespace storage { class SpecialStoragePolicy; +struct BucketInfo; } // Helper class that counts the number of unique origins, that are affected by @@ -52,9 +49,8 @@ const scoped_refptr<storage::SpecialStoragePolicy>& special_storage_policy, const std::vector<content::StorageUsageInfo>& infos); - void GetQuotaStorageKeysCallback( - const std::set<blink::StorageKey>& storage_keys, - blink::mojom::StorageType type); + void GetQuotaBucketsCallback(const std::set<storage::BucketInfo>& buckets, + blink::mojom::StorageType type); void SitesWithMediaLicensesCallback( const std::list<BrowsingDataMediaLicenseHelper::MediaLicenseInfo>& media_license_info_list);
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc index 53048514..aba20152 100644 --- a/chrome/browser/chrome_browser_interface_binders.cc +++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -183,6 +183,8 @@ #include "chrome/browser/ui/webui/app_management/app_management.mojom.h" #include "chrome/browser/ui/webui/chromeos/add_supervision/add_supervision.mojom.h" #include "chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_ui.h" +#include "chrome/browser/ui/webui/chromeos/audio/audio.mojom.h" +#include "chrome/browser/ui/webui/chromeos/audio/audio_ui.h" #include "chrome/browser/ui/webui/chromeos/crostini_installer/crostini_installer.mojom.h" #include "chrome/browser/ui/webui/chromeos/crostini_installer/crostini_installer_ui.h" #include "chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader.mojom.h" @@ -918,6 +920,10 @@ chromeos::bluetooth_config::mojom::CrosBluetoothConfig, chromeos::settings::OSSettingsUI>(map); } + + RegisterWebUIControllerInterfaceBinder<audio::mojom::PageHandlerFactory, + chromeos::AudioUI>(map); + #endif // BUILDFLAG(IS_CHROMEOS_ASH) #if BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OFFICIAL_BUILD)
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index ffc78361..9c1ef9f 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc
@@ -4026,7 +4026,7 @@ std::make_unique<CertificateReportingServiceCertReporter>(web_contents), base::BindOnce(&HandleSSLErrorWrapper), base::BindOnce(&IsInHostedApp), base::BindOnce( - &ShouldIgnoreInterstitialBecauseNavigationDefaultedToHttps))); + &ShouldIgnoreSslInterstitialBecauseNavigationDefaultedToHttps))); throttles.push_back(std::make_unique<LoginNavigationThrottle>(handle));
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn index a445e13..28ed0fc 100644 --- a/chrome/browser/chromeos/BUILD.gn +++ b/chrome/browser/chromeos/BUILD.gn
@@ -271,7 +271,6 @@ "//chromeos/services/device_sync:stub_device_sync", "//chromeos/services/device_sync/public/cpp", "//chromeos/services/ime:constants", - "//chromeos/services/ime/public/cpp:buildflags", "//chromeos/services/ime/public/cpp:structs", "//chromeos/services/ime/public/mojom", "//chromeos/services/machine_learning/public/cpp", @@ -2547,8 +2546,6 @@ "../ash/settings/supervised_user_cros_settings_provider.h", "../ash/settings/token_encryptor.cc", "../ash/settings/token_encryptor.h", - "../ash/sharesheet/cros_sharesheet_service_delegate.cc", - "../ash/sharesheet/cros_sharesheet_service_delegate.h", "../ash/smb_client/discovery/host_locator.h", "../ash/smb_client/discovery/in_memory_host_locator.cc", "../ash/smb_client/discovery/in_memory_host_locator.h",
diff --git a/chrome/browser/chromeos/extensions/wallpaper_api.cc b/chrome/browser/chromeos/extensions/wallpaper_api.cc index efc263c..dc248b9 100644 --- a/chrome/browser/chromeos/extensions/wallpaper_api.cc +++ b/chrome/browser/chromeos/extensions/wallpaper_api.cc
@@ -156,8 +156,6 @@ // Gets account id from the caller, ensuring multiprofile compatibility. const user_manager::User* user = GetUserFromBrowserContext(browser_context()); account_id_ = user->GetAccountId(); - wallpaper_files_id_ = - WallpaperControllerClientImpl::Get()->GetFilesId(account_id_); if (params_->details.data) { StartDecode(*params_->details.data); @@ -188,7 +186,7 @@ const std::string file_name = base::FilePath(params_->details.filename).BaseName().value(); WallpaperControllerClientImpl::Get()->SetCustomWallpaper( - account_id_, wallpaper_files_id_, file_name, layout, image, + account_id_, file_name, layout, image, /*preview_mode=*/false); unsafe_wallpaper_decoder_ = nullptr;
diff --git a/chrome/browser/chromeos/extensions/wallpaper_api.h b/chrome/browser/chromeos/extensions/wallpaper_api.h index 44d3ecc..9448877 100644 --- a/chrome/browser/chromeos/extensions/wallpaper_api.h +++ b/chrome/browser/chromeos/extensions/wallpaper_api.h
@@ -45,9 +45,6 @@ // User id of the user who initiate this API call. AccountId account_id_ = EmptyAccountId(); - - // Id used to identify user wallpaper files on hard drive. - std::string wallpaper_files_id_; }; #endif // CHROME_BROWSER_CHROMEOS_EXTENSIONS_WALLPAPER_API_H_
diff --git a/chrome/browser/chromeos/extensions/wallpaper_private_api.cc b/chrome/browser/chromeos/extensions/wallpaper_private_api.cc index 9284c88..f9c0e32 100644 --- a/chrome/browser/chromeos/extensions/wallpaper_private_api.cc +++ b/chrome/browser/chromeos/extensions/wallpaper_private_api.cc
@@ -425,8 +425,6 @@ // Gets account id from the caller, ensuring multiprofile compatibility. const user_manager::User* user = GetUserFromBrowserContext(browser_context()); account_id_ = user->GetAccountId(); - wallpaper_files_id_ = - WallpaperControllerClientImpl::Get()->GetFilesId(account_id_); StartDecode(params->wallpaper); @@ -442,8 +440,7 @@ const std::string file_name = base::FilePath(params->file_name).BaseName().value(); WallpaperControllerClientImpl::Get()->SetCustomWallpaper( - account_id_, wallpaper_files_id_, file_name, layout, image, - params->preview_mode); + account_id_, file_name, layout, image, params->preview_mode); unsafe_wallpaper_decoder_ = nullptr; if (params->generate_thumbnail) {
diff --git a/chrome/browser/chromeos/extensions/wallpaper_private_api.h b/chrome/browser/chromeos/extensions/wallpaper_private_api.h index 25c1d001..f0cb515 100644 --- a/chrome/browser/chromeos/extensions/wallpaper_private_api.h +++ b/chrome/browser/chromeos/extensions/wallpaper_private_api.h
@@ -135,9 +135,6 @@ // User account id of the active user when this api is been called. AccountId account_id_ = EmptyAccountId(); - - // User id hash of the logged in user. - std::string wallpaper_files_id_; }; class WallpaperPrivateSetCustomWallpaperLayoutFunction
diff --git a/chrome/browser/download/android/download_open_source.h b/chrome/browser/download/android/download_open_source.h index 61a7f27..cf529af2 100644 --- a/chrome/browser/download/android/download_open_source.h +++ b/chrome/browser/download/android/download_open_source.h
@@ -38,7 +38,9 @@ kOfflineIndicator = 11, // Android notification for offline content. kOfflineContentNotification = 12, - kMaxValue = kOfflineContentNotification + // Download progress message. + kDownloadProgressMessage = 13, + kMaxValue = kDownloadProgressMessage }; #endif // CHROME_BROWSER_DOWNLOAD_ANDROID_DOWNLOAD_OPEN_SOURCE_H_
diff --git a/chrome/browser/extensions/navigation_observer_browsertest.cc b/chrome/browser/extensions/navigation_observer_browsertest.cc index eb5135b..aaa2414 100644 --- a/chrome/browser/extensions/navigation_observer_browsertest.cc +++ b/chrome/browser/extensions/navigation_observer_browsertest.cc
@@ -223,17 +223,23 @@ // The SiteInstance of the disabled extension frame should be different from // the SiteInstance of the enabled extension subframe. It should reference the // invalid extension ID or the error page URL. - EXPECT_NE(subframe->GetSiteInstance(), extension_site_instance); - if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(false)) { - EXPECT_EQ(subframe->GetSiteInstance()->GetSiteURL(), - GURL(content::kUnreachableWebDataURL)); - } else { - EXPECT_EQ(subframe->GetSiteInstance()->GetSiteURL(), - GURL(chrome::kExtensionInvalidRequestURL)); - // The disabled extension process should only be locked if strict extension - // isolation is enabled. - EXPECT_EQ(IsStrictExtensionIsolationEnabled(), - subframe->GetProcess()->IsProcessLockedToSiteForTesting()); + // TODO(crbug.com/1234637): remove the exception for the + // SubframeShutdownDelay experiment below. It is temporary, intended to allow + // the experiment to proceed while the reason for it causing + // |extension_site_instance| to be reused is addressed separately. + if (!base::FeatureList::IsEnabled(features::kSubframeShutdownDelay)) { + EXPECT_NE(subframe->GetSiteInstance(), extension_site_instance); + if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(false)) { + EXPECT_EQ(subframe->GetSiteInstance()->GetSiteURL(), + GURL(content::kUnreachableWebDataURL)); + } else { + EXPECT_EQ(subframe->GetSiteInstance()->GetSiteURL(), + GURL(chrome::kExtensionInvalidRequestURL)); + // The disabled extension process should only be locked if strict + // extension isolation is enabled. + EXPECT_EQ(IsStrictExtensionIsolationEnabled(), + subframe->GetProcess()->IsProcessLockedToSiteForTesting()); + } } // Re-enable the extension.
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc index 28970f4..24afa01 100644 --- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc +++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
@@ -106,17 +106,6 @@ const char kPathTypeKey[] = "path-type"; const char kTimestampKey[] = "timestamp"; -// TODO(https://crbug.com/1177334): Remove migration logic. -// Deprecated 2/2021. Former schema (per origin): -// { -// ... -// "default-path" : <path>, -// "default-path-type" : <type>, -// ... -// } -const char kDeprecatedLastPickedDirectoryKey[] = "default-path"; -const char kDeprecatedLastPickedDirectoryTypeKey[] = "default-path-type"; - void ShowFileSystemAccessRestrictedDirectoryDialogOnUIThread( content::GlobalRenderFrameHostId frame_id, const url::Origin& origin, @@ -1162,45 +1151,6 @@ std::move(result_callback))); } -// TODO(https://crbug.com/1177334): Remove migration logic. -void ChromeFileSystemAccessPermissionContext::MaybeMigrateOriginToNewSchema( - const url::Origin& origin) { - std::unique_ptr<base::Value> value = content_settings()->GetWebsiteSetting( - origin.GetURL(), origin.GetURL(), - ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, /*info=*/nullptr); - - if (!value) - return; - - auto* default_path_value = value->FindKey(kDeprecatedLastPickedDirectoryKey); - if (!default_path_value) - return; - - auto default_path = - base::ValueToFilePath(default_path_value).value_or(base::FilePath()); - auto default_type = - value->FindIntKey(kDeprecatedLastPickedDirectoryTypeKey) == - static_cast<int>(PathType::kExternal) - ? PathType::kExternal - : PathType::kLocal; - - // Remove old keys. - value->RemoveKey(kDeprecatedLastPickedDirectoryKey); - value->RemoveKey(kDeprecatedLastPickedDirectoryTypeKey); - - // Set this information as the default. - base::Value entry(base::Value::Type::DICTIONARY); - entry.SetKey(kPathKey, base::FilePathToValue(default_path)); - entry.SetIntKey(kPathTypeKey, static_cast<int>(default_type)); - - value->SetKey(GenerateLastPickedDirectoryKey(std::string()), - std::move(entry)); - - content_settings_->SetWebsiteSettingDefaultScope( - origin.GetURL(), origin.GetURL(), - ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, std::move(value)); -} - void ChromeFileSystemAccessPermissionContext::MaybeEvictEntries( std::unique_ptr<base::Value>& value) { if (!value->is_dict()) { @@ -1235,8 +1185,6 @@ const std::string& id, const base::FilePath& path, const PathType type) { - MaybeMigrateOriginToNewSchema(origin); - std::unique_ptr<base::Value> value = content_settings()->GetWebsiteSetting( origin.GetURL(), origin.GetURL(), ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, /*info=*/nullptr); @@ -1262,8 +1210,6 @@ ChromeFileSystemAccessPermissionContext::GetLastPickedDirectory( const url::Origin& origin, const std::string& id) { - MaybeMigrateOriginToNewSchema(origin); - std::unique_ptr<base::Value> value = content_settings()->GetWebsiteSetting( origin.GetURL(), origin.GetURL(), ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, /*info=*/nullptr);
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context_unittest.cc b/chrome/browser/file_system_access/chrome_file_system_access_permission_context_unittest.cc index 270ed18..fbfa2a8 100644 --- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context_unittest.cc +++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context_unittest.cc
@@ -645,45 +645,6 @@ new_path); } -// TODO(https://crbug.com/1177334): Remove test when removing migration logic. -TEST_F(ChromeFileSystemAccessPermissionContextTest, - Migrate_LastPickedDirectory) { - EXPECT_EQ(permission_context() - ->GetLastPickedDirectory(kTestOrigin, kTestStartingDirectoryId) - .path, - base::FilePath()); - - // Set keys using the old method. - const char kDeprecatedLastPickedDirectoryKey[] = "default-path"; - const char kDeprecatedLastPickedDirectoryTypeKey[] = "default-path-type"; - const base::FilePath path = base::FilePath(FILE_PATH_LITERAL("/baz/bar")); - const auto type = PathType::kExternal; - base::Value dict(base::Value::Type::DICTIONARY); - dict.SetKey(kDeprecatedLastPickedDirectoryKey, base::FilePathToValue(path)); - dict.SetIntKey(kDeprecatedLastPickedDirectoryTypeKey, static_cast<int>(type)); - HostContentSettingsMapFactory::GetForProfile(&profile_) - ->SetWebsiteSettingDefaultScope( - kTestOrigin.GetURL(), kTestOrigin.GetURL(), - ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, - base::Value::ToUniquePtrValue(std::move(dict))); - - // Retrieve key using the new method. Information should have been migrated. - auto result = permission_context()->GetLastPickedDirectory( - kTestOrigin, /*id=*/std::string()); - EXPECT_EQ(result.path, path); - EXPECT_EQ(result.type, type); - - // Confirm that the old keys have been removed. - std::unique_ptr<base::Value> value = - HostContentSettingsMapFactory::GetForProfile(&profile_) - ->GetWebsiteSetting( - kTestOrigin.GetURL(), kTestOrigin.GetURL(), - ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, - /*info=*/nullptr); - EXPECT_FALSE(value->FindKey(kDeprecatedLastPickedDirectoryKey)); - EXPECT_FALSE(value->FindIntKey(kDeprecatedLastPickedDirectoryKey)); -} - TEST_F(ChromeFileSystemAccessPermissionContextTest, GetWellKnownDirectoryPath_Base_OK) { base::ScopedPathOverride user_desktop_override(
diff --git a/chrome/browser/language/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java b/chrome/browser/language/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java index 3f0d0e8..155d96f 100644 --- a/chrome/browser/language/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java +++ b/chrome/browser/language/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
@@ -28,7 +28,10 @@ } /** - * Initates a translation on the given tab to the given target language. + * Initates a translation on the given tab to the given target language. All metrics reported + * for this translation assume that this translation was initiated from the Translate UI. If the + * translation from the Context Menu or by some other means, then an extra signal needs to be + * passed through. */ public static void translateTabToLanguage(Tab tab, String targetLanguageCode) { TranslateBridgeJni.get().translateToLanguage(tab.getWebContents(), targetLanguageCode);
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.cc b/chrome/browser/metrics/chrome_metrics_service_client.cc index a261bebf..bde4ce2 100644 --- a/chrome/browser/metrics/chrome_metrics_service_client.cc +++ b/chrome/browser/metrics/chrome_metrics_service_client.cc
@@ -929,10 +929,6 @@ } bool ChromeMetricsServiceClient::RegisterForNotifications() { - registrar_.Add(this, content::NOTIFICATION_LOAD_START, - content::NotificationService::AllSources()); - registrar_.Add(this, content::NOTIFICATION_LOAD_STOP, - content::NotificationService::AllSources()); registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, content::NotificationService::AllSources()); registrar_.Add(this, content::NOTIFICATION_RENDER_WIDGET_HOST_HANG, @@ -1021,8 +1017,6 @@ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); switch (type) { - case content::NOTIFICATION_LOAD_STOP: - case content::NOTIFICATION_LOAD_START: case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: case content::NOTIFICATION_RENDER_WIDGET_HOST_HANG: metrics_service_->OnApplicationNotIdle(); @@ -1045,6 +1039,10 @@ } } +void ChromeMetricsServiceClient::LoadingStateChanged(bool /*is_loading*/) { + metrics_service_->OnApplicationNotIdle(); +} + void ChromeMetricsServiceClient::OnURLOpenedFromOmnibox(OmniboxLog* log) { metrics_service_->OnApplicationNotIdle(); }
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.h b/chrome/browser/metrics/chrome_metrics_service_client.h index 1e5a8ab..59983177 100644 --- a/chrome/browser/metrics/chrome_metrics_service_client.h +++ b/chrome/browser/metrics/chrome_metrics_service_client.h
@@ -78,6 +78,7 @@ const metrics::MetricsLogUploader::UploadCallback& on_upload_complete) override; base::TimeDelta GetStandardUploadInterval() override; + void LoadingStateChanged(bool is_loading) override; void OnPluginLoadingError(const base::FilePath& plugin_path) override; bool IsReportingPolicyManaged() override; metrics::EnableMetricsDefault GetMetricsReportingDefaultState() override;
diff --git a/chrome/browser/metrics/metrics_services_web_contents_observer.cc b/chrome/browser/metrics/metrics_services_web_contents_observer.cc new file mode 100644 index 0000000..e76b293 --- /dev/null +++ b/chrome/browser/metrics/metrics_services_web_contents_observer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/metrics/metrics_services_web_contents_observer.h" + +#include "chrome/browser/browser_process.h" +#include "components/metrics_services_manager/metrics_services_manager.h" + +namespace metrics { + +MetricsServicesWebContentsObserver::MetricsServicesWebContentsObserver( + content::WebContents* web_contents) + : content::WebContentsObserver(web_contents) {} + +MetricsServicesWebContentsObserver::~MetricsServicesWebContentsObserver() = + default; + +void MetricsServicesWebContentsObserver::DidStartLoading() { + auto* manager = g_browser_process->GetMetricsServicesManager(); + if (manager) + manager->LoadingStateChanged(/*is_loading=*/true); +} + +void MetricsServicesWebContentsObserver::DidStopLoading() { + auto* manager = g_browser_process->GetMetricsServicesManager(); + if (manager) + manager->LoadingStateChanged(/*is_loading=*/false); +} + +WEB_CONTENTS_USER_DATA_KEY_IMPL(MetricsServicesWebContentsObserver) + +} // namespace metrics
diff --git a/chrome/browser/metrics/metrics_services_web_contents_observer.h b/chrome/browser/metrics/metrics_services_web_contents_observer.h new file mode 100644 index 0000000..5ef607f --- /dev/null +++ b/chrome/browser/metrics/metrics_services_web_contents_observer.h
@@ -0,0 +1,37 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_METRICS_METRICS_SERVICES_WEB_CONTENTS_OBSERVER_H_ +#define CHROME_BROWSER_METRICS_METRICS_SERVICES_WEB_CONTENTS_OBSERVER_H_ + +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" + +namespace metrics { + +class MetricsServicesWebContentsObserver + : public content::WebContentsObserver, + public content::WebContentsUserData<MetricsServicesWebContentsObserver> { + public: + ~MetricsServicesWebContentsObserver() override; + + private: + explicit MetricsServicesWebContentsObserver( + content::WebContents* web_contents); + MetricsServicesWebContentsObserver( + const MetricsServicesWebContentsObserver&) = delete; + MetricsServicesWebContentsObserver& operator=( + const MetricsServicesWebContentsObserver&) = delete; + friend class content::WebContentsUserData<MetricsServicesWebContentsObserver>; + + // content::WebContentsObserver overrides: + void DidStartLoading() override; + void DidStopLoading() override; + + WEB_CONTENTS_USER_DATA_KEY_DECL(); +}; + +} // namespace metrics + +#endif // CHROME_BROWSER_METRICS_METRICS_SERVICES_WEB_CONTENTS_OBSERVER_H_
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc index 25a180c7..9013206 100644 --- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc +++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
@@ -16,6 +16,7 @@ #include "chrome/browser/nearby_sharing/logging/logging.h" #include "chrome/browser/nearby_sharing/nearby_sharing_service.h" #include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h" +#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/sharesheet/sharesheet_types.h" #include "chrome/browser/ui/browser_navigator.h" @@ -118,7 +119,7 @@ } // namespace -NearbyShareAction::NearbyShareAction() = default; +NearbyShareAction::NearbyShareAction(Profile* profile) : profile_(profile) {} NearbyShareAction::~NearbyShareAction() = default; @@ -137,8 +138,7 @@ gfx::Size size = ComputeSize(); controller->SetBubbleSize(size.width(), size.height()); - auto* profile = controller->GetProfile(); - auto view = std::make_unique<views::WebView>(profile); + auto view = std::make_unique<views::WebView>(profile_); // If this is not done, we don't see anything in our view. view->SetPreferredSize(size); web_view_ = root_view->AddChildView(std::move(view)); @@ -162,7 +162,7 @@ nearby_ui->SetSharesheetController(controller); nearby_ui->SetAttachments( - CreateAttachmentsFromIntent(profile, std::move(intent))); + CreateAttachmentsFromIntent(profile_, std::move(intent))); } bool NearbyShareAction::ShouldShowAction(const apps::mojom::IntentPtr& intent, @@ -196,13 +196,8 @@ if (nearby_share_disabled_by_policy_for_testing_.has_value()) { return *nearby_share_disabled_by_policy_for_testing_; } - - Profile* profile = ProfileManager::GetActiveUserProfile(); - if (!profile) { - return false; - } NearbySharingService* nearby_sharing_service = - NearbySharingServiceFactory::GetForBrowserContext(profile); + NearbySharingServiceFactory::GetForBrowserContext(profile_); if (!nearby_sharing_service) { return false; } @@ -236,13 +231,8 @@ if (callback.is_null()) { return; } - Profile* profile = ProfileManager::GetActiveUserProfile(); - if (!profile) { - std::move(callback).Run(); - return; - } NearbySharingService* nearby_sharing_service = - NearbySharingServiceFactory::GetForBrowserContext(profile); + NearbySharingServiceFactory::GetForBrowserContext(profile_); if (!nearby_sharing_service) { std::move(callback).Run(); return; @@ -264,8 +254,7 @@ const std::string& frame_name, const GURL& target_url, content::WebContents* new_contents) { - chrome::ScopedTabbedBrowserDisplayer displayer( - Profile::FromBrowserContext(web_view_->GetBrowserContext())); + chrome::ScopedTabbedBrowserDisplayer displayer(profile_); NavigateParams nav_params(displayer.browser(), target_url, ui::PageTransition::PAGE_TRANSITION_LINK); Navigate(&nav_params);
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h index 5a1baa0..bb9b286 100644 --- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h +++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h
@@ -10,6 +10,8 @@ #include "content/public/browser/web_contents_delegate.h" #include "ui/views/controls/webview/unhandled_keyboard_event_handler.h" +class Profile; + namespace views { class WebView; } // namespace views @@ -17,7 +19,7 @@ class NearbyShareAction : public sharesheet::ShareAction, content::WebContentsDelegate { public: - NearbyShareAction(); + explicit NearbyShareAction(Profile* profile); ~NearbyShareAction() override; NearbyShareAction(const NearbyShareAction&) = delete; NearbyShareAction& operator=(const NearbyShareAction&) = delete; @@ -57,6 +59,7 @@ private: bool IsNearbyShareDisabledByPolicy(); + Profile* profile_; absl::optional<bool> nearby_share_disabled_by_policy_for_testing_ = absl::nullopt; views::WebView* web_view_;
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action_unittest.cc b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action_unittest.cc index e7abab8..3f78d30 100644 --- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action_unittest.cc +++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action_unittest.cc
@@ -57,10 +57,11 @@ ASSERT_TRUE(profile_manager_->SetUp()); profile_ = profile_manager_->CreateTestingProfile("testing_profile"); - nearby_share_action_.SetNearbyShareDisabledByPolicyForTesting(false); + nearby_share_action_ = std::make_unique<NearbyShareAction>(profile_); + nearby_share_action_->SetNearbyShareDisabledByPolicyForTesting(false); // Calling the cleanup callback means an error occurred in the function. ASSERT_DEATH(ActionCleanupCallbackStub(), ""); - nearby_share_action_.SetActionCleanupCallbackForArc( + nearby_share_action_->SetActionCleanupCallbackForArc( base::BindOnce(&ActionCleanupCallbackStub)); } @@ -126,13 +127,13 @@ content::BrowserTaskEnvironment task_environment_; std::unique_ptr<TestingProfileManager> profile_manager_; Profile* profile_; - NearbyShareAction nearby_share_action_; + std::unique_ptr<NearbyShareAction> nearby_share_action_; }; TEST_F(NearbyShareActionTest, ShouldShowAction) { for (auto& test_case : GetIntentTestCases()) { EXPECT_EQ( - nearby_share_action_.ShouldShowAction( + nearby_share_action_->ShouldShowAction( std::move(test_case.intent), test_case.contains_hosted_document), test_case.should_show_action); }
diff --git a/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc b/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc index f8313d9..c92deff 100644 --- a/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc +++ b/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc
@@ -16,6 +16,7 @@ #include "components/ukm/test_ukm_recorder.h" #include "content/public/browser/notification_types.h" #include "content/public/test/browser_test.h" +#include "content/public/test/prerender_test_util.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "services/metrics/public/cpp/ukm_builders.h" @@ -355,4 +356,73 @@ EXPECT_FALSE(GetObserverForActiveWebContents()); } +class BlinkOptimizationGuidePrerenderBrowserTest + : public BlinkOptimizationGuideBrowserTest { + public: + BlinkOptimizationGuidePrerenderBrowserTest() = default; + ~BlinkOptimizationGuidePrerenderBrowserTest() override = default; + BlinkOptimizationGuidePrerenderBrowserTest( + const BlinkOptimizationGuidePrerenderBrowserTest&) = delete; + + BlinkOptimizationGuidePrerenderBrowserTest& operator=( + const BlinkOptimizationGuidePrerenderBrowserTest&) = delete; + + void SetUpCommandLine(base::CommandLine* command_line) override { + prerender_helper_ = std::make_unique<content::test::PrerenderTestHelper>( + base::BindRepeating( + &BlinkOptimizationGuidePrerenderBrowserTest::GetWebContents, + base::Unretained(this))); + } + + void SetUpOnMainThread() override { + prerender_helper_->SetUpOnMainThread(embedded_test_server()); + BlinkOptimizationGuideBrowserTest::SetUpOnMainThread(); + } + + content::test::PrerenderTestHelper& prerender_test_helper() { + return *prerender_helper_; + } + + content::WebContents* GetWebContents() { + return browser()->tab_strip_model()->GetActiveWebContents(); + } + + private: + std::unique_ptr<content::test::PrerenderTestHelper> prerender_helper_; +}; + +// Instantiates test cases for each optimization type. +INSTANTIATE_TEST_SUITE_P( + All, + BlinkOptimizationGuidePrerenderBrowserTest, + testing::Combine( + // The optimization type. + testing::Values( + proto::OptimizationType::DELAY_ASYNC_SCRIPT_EXECUTION, + proto::OptimizationType::DELAY_COMPETING_LOW_PRIORITY_REQUESTS), + // Whether the feature flag for the optimization type is enabled. + testing::Bool())); + +IN_PROC_BROWSER_TEST_P(BlinkOptimizationGuidePrerenderBrowserTest, + PrerenderingDontCreateBlinkOptimizationGuideInquirer) { + GURL initial_url = GetURLWithMockHost("/empty.html"); + GURL prerender_url = GetURLWithMockHost("/simple.html"); + ASSERT_NE(ui_test_utils::NavigateToURL(browser(), initial_url), nullptr); + auto weak_prev_inquirer = GetCurrentInquirer()->GetWeakPtrForTesting(); + EXPECT_TRUE(weak_prev_inquirer); + + // Load a page in the prerender. This shouldn't create a new inquirer yet. + int host_id = prerender_test_helper().AddPrerender(prerender_url); + content::test::PrerenderHostObserver host_observer(*GetWebContents(), + host_id); + EXPECT_FALSE(host_observer.was_activated()); + EXPECT_TRUE(weak_prev_inquirer); + + // Activate the prerendered page. This should create a new inquirer. + prerender_test_helper().NavigatePrimaryPage(prerender_url); + EXPECT_TRUE(host_observer.was_activated()); + EXPECT_FALSE(weak_prev_inquirer); + EXPECT_TRUE(GetCurrentInquirer()->GetWeakPtrForTesting()); +} + } // namespace optimization_guide
diff --git a/chrome/browser/optimization_guide/blink/blink_optimization_guide_inquirer.h b/chrome/browser/optimization_guide/blink/blink_optimization_guide_inquirer.h index 84dcefe..b8d13a6 100644 --- a/chrome/browser/optimization_guide/blink/blink_optimization_guide_inquirer.h +++ b/chrome/browser/optimization_guide/blink/blink_optimization_guide_inquirer.h
@@ -44,6 +44,10 @@ return optimization_guide_hints_.Clone(); } + base::WeakPtr<BlinkOptimizationGuideInquirer> GetWeakPtrForTesting() { + return weak_ptr_factory_.GetWeakPtr(); + } + private: BlinkOptimizationGuideInquirer();
diff --git a/chrome/browser/optimization_guide/blink/blink_optimization_guide_web_contents_observer.cc b/chrome/browser/optimization_guide/blink/blink_optimization_guide_web_contents_observer.cc index e8bcae8..0cccd55 100644 --- a/chrome/browser/optimization_guide/blink/blink_optimization_guide_web_contents_observer.cc +++ b/chrome/browser/optimization_guide/blink/blink_optimization_guide_web_contents_observer.cc
@@ -27,10 +27,8 @@ content::NavigationHandle* navigation_handle) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - // Currently the optimization guide supports only the main frame navigation. - // TODO(https://crbug.com/1218946): With MPArch there may be multiple main - // frames. This caller was converted automatically to the primary main frame - // to preserve its semantics. Follow up to confirm correctness. + // Currently the optimization guide supports only the primary main frame + // navigation. if (!navigation_handle->IsInPrimaryMainFrame() || navigation_handle->IsSameDocument()) { return;
diff --git a/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc b/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc index 3600c826..af0a6ae5 100644 --- a/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc +++ b/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc
@@ -54,7 +54,6 @@ #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/navigation_handle.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" #include "services/metrics/public/cpp/ukm_source.h" @@ -210,12 +209,10 @@ class ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder { public: explicit ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder( - content::NavigationHandle* navigation_handle) + OptimizationGuideNavigationData* navigation_data) : race_attempt_status_( optimization_guide::RaceNavigationFetchAttemptStatus::kUnknown), - navigation_data_( - OptimizationGuideKeyedService:: - GetNavigationDataFromNavigationHandle(navigation_handle)) {} + navigation_data_(navigation_data) {} ~ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder() { DCHECK_NE(race_attempt_status_, @@ -833,12 +830,10 @@ last_attempt_time.ToDeltaSinceWindowsEpoch().InMicroseconds()); } -void OptimizationGuideHintsManager::LoadHintForNavigation( - content::NavigationHandle* navigation_handle, - base::OnceClosure callback) { +void OptimizationGuideHintsManager::LoadHintForURL(const GURL& url, + base::OnceClosure callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - const auto& url = navigation_handle->GetURL(); if (!url.has_host()) { std::move(callback).Run(); return; @@ -1284,37 +1279,33 @@ } void OptimizationGuideHintsManager::OnNavigationStartOrRedirect( - content::NavigationHandle* navigation_handle, + OptimizationGuideNavigationData* navigation_data, base::OnceClosure callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - LoadHintForNavigation(navigation_handle, std::move(callback)); + LoadHintForURL(navigation_data->navigation_url(), std::move(callback)); if (optimization_guide::switches:: DisableFetchingHintsAtNavigationStartForTesting()) { return; } - MaybeFetchHintsForNavigation(navigation_handle); + MaybeFetchHintsForNavigation(navigation_data); } void OptimizationGuideHintsManager::MaybeFetchHintsForNavigation( - content::NavigationHandle* navigation_handle) { + OptimizationGuideNavigationData* navigation_data) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (registered_optimization_types_.empty()) return; - const GURL url = navigation_handle->GetURL(); + const GURL url = navigation_data->navigation_url(); if (!IsAllowedToFetchNavigationHints(url)) return; ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder - race_navigation_recorder(navigation_handle); - - OptimizationGuideNavigationData* navigation_data = - OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle( - navigation_handle); + race_navigation_recorder(navigation_data); // We expect that if the URL is being fetched for, we have already run through // the logic to decide if we also require fetching hints for the host. @@ -1325,8 +1316,7 @@ // Just set the hints fetch start to the start of the navigation, so we can // track whether the hint came back before commit or not. - navigation_data->set_hints_fetch_start( - navigation_handle->NavigationStart()); + navigation_data->set_hints_fetch_start(navigation_data->navigation_start()); return; }
diff --git a/chrome/browser/optimization_guide/optimization_guide_hints_manager.h b/chrome/browser/optimization_guide/optimization_guide_hints_manager.h index 3d0dc404..b7ed3fc 100644 --- a/chrome/browser/optimization_guide/optimization_guide_hints_manager.h +++ b/chrome/browser/optimization_guide/optimization_guide_hints_manager.h
@@ -28,10 +28,6 @@ #include "components/optimization_guide/proto/models.pb.h" #include "third_party/abseil-cpp/absl/types/optional.h" -namespace content { -class NavigationHandle; -} // namespace content - namespace network { class SharedURLLoaderFactory; } // namespace network @@ -143,12 +139,13 @@ // Overrides |clock_| for testing. void SetClockForTesting(const base::Clock* clock); - // Notifies |this| that a navigation with |navigation_handle| has started. + // Notifies |this| that a navigation with |navigation_data| started. // |callback| is run when the request has finished regardless of whether there // was actually a hint for that load or not. The callback can be used as a // signal for tests. - void OnNavigationStartOrRedirect(content::NavigationHandle* navigation_handle, - base::OnceClosure callback); + void OnNavigationStartOrRedirect( + OptimizationGuideNavigationData* navigation_data, + base::OnceClosure callback); // Notifies |this| that a navigation with redirect chain // |navigation_redirect_chain| has finished. @@ -298,12 +295,11 @@ // |url|. bool IsAllowedToFetchNavigationHints(const GURL& url); - // Loads the hint if available. + // Loads the hint if available for navigation to |url|. // |callback| is run when the request has finished regardless of whether there // was actually a hint for that load or not. The callback can be used as a // signal for tests. - void LoadHintForNavigation(content::NavigationHandle* navigation_handle, - base::OnceClosure callback); + void LoadHintForURL(const GURL& url, base::OnceClosure callback); // Loads the hint for |host| if available. // |callback| is run when the request has finished regardless of whether there @@ -330,11 +326,12 @@ // optimization types are covered by optimization filters. bool HasOptimizationTypeToFetchFor(); - // Creates a hints fetch for |navigation_handle| if it is allowed. The - // fetch will include the host and URL of the |navigation_handle| if the - // associated hints for each are not already in the cache. + // Creates a hints fetch for navigation represented by |navigation_data|, if + // it is allowed. The fetch will include the host and URL of the + // |navigation_data| if the associated hints for each are not already in the + // cache. void MaybeFetchHintsForNavigation( - content::NavigationHandle* navigation_handle); + OptimizationGuideNavigationData* navigation_data); // If an entry for |navigation_url| is contained in |registered_callbacks_|, // it will load the hint for |navigation_url|'s host and upon completion, will
diff --git a/chrome/browser/optimization_guide/optimization_guide_hints_manager_unittest.cc b/chrome/browser/optimization_guide/optimization_guide_hints_manager_unittest.cc index f104d83..171dda2 100644 --- a/chrome/browser/optimization_guide/optimization_guide_hints_manager_unittest.cc +++ b/chrome/browser/optimization_guide/optimization_guide_hints_manager_unittest.cc
@@ -16,7 +16,6 @@ #include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" #include "chrome/browser/optimization_guide/optimization_guide_tab_url_provider.h" #include "chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h" #include "chrome/test/base/testing_profile.h" @@ -426,6 +425,28 @@ return navigation_handle; } + void CallOnNavigationStartOrRedirect( + content::NavigationHandle* navigation_handle, + base::OnceClosure callback) { + OptimizationGuideNavigationData* navigation_data = + GetNavigationDataFromNavigationHandle(navigation_handle); + navigation_data->set_navigation_url(navigation_handle->GetURL()); + hints_manager()->OnNavigationStartOrRedirect(navigation_data, + std::move(callback)); + } + + static OptimizationGuideNavigationData* GetNavigationDataFromNavigationHandle( + content::NavigationHandle* navigation_handle) { + OptimizationGuideWebContentsObserver* + optimization_guide_web_contents_observer = + OptimizationGuideWebContentsObserver::FromWebContents( + navigation_handle->GetWebContents()); + if (!optimization_guide_web_contents_observer) + return nullptr; + return optimization_guide_web_contents_observer + ->GetOrCreateOptimizationGuideNavigationData(navigation_handle); + } + void SetConnectionOffline() { network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType( network::mojom::ConnectionType::CONNECTION_NONE); @@ -481,7 +502,6 @@ } content::BrowserTaskEnvironment task_environment_{ - base::test::TaskEnvironment::MainThreadType::UI, base::test::TaskEnvironment::TimeSource::MOCK_TIME}; base::test::ScopedFeatureList scoped_feature_list_; TestingProfile testing_profile_; @@ -854,8 +874,8 @@ navigation_handle->set_has_committed(true); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); histogram_tester.ExpectUniqueSample("OptimizationGuide.LoadedHint.Result", @@ -871,8 +891,8 @@ url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); histogram_tester.ExpectUniqueSample("OptimizationGuide.LoadedHint.Result", @@ -888,8 +908,8 @@ GURL("https://notinhints.com")); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); histogram_tester.ExpectUniqueSample("OptimizationGuide.LoadedHint.Result", @@ -905,8 +925,8 @@ GURL("blargh")); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); histogram_tester.ExpectTotalCount("OptimizationGuide.LoadedHint.Result", 0); @@ -1305,8 +1325,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); optimization_guide::OptimizationMetadata optimization_metadata; @@ -1338,8 +1358,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); ukm::TestAutoSetUkmRecorder ukm_recorder; @@ -1386,8 +1406,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); ukm::TestAutoSetUkmRecorder ukm_recorder; @@ -1436,8 +1456,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); ukm::TestAutoSetUkmRecorder ukm_recorder; @@ -1486,8 +1506,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); ukm::TestAutoSetUkmRecorder ukm_recorder; @@ -1533,8 +1553,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); ukm::TestAutoSetUkmRecorder ukm_recorder; @@ -1572,8 +1592,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); ukm::TestAutoSetUkmRecorder ukm_recorder; @@ -1602,8 +1622,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); optimization_guide::OptimizationTypeDecision optimization_type_decision = @@ -1642,8 +1662,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); optimization_guide::OptimizationMetadata optimization_metadata; @@ -1679,8 +1699,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); optimization_guide::OptimizationMetadata optimization_metadata; @@ -1718,8 +1738,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); optimization_guide::OptimizationMetadata optimization_metadata; @@ -1759,8 +1779,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); optimization_guide::OptimizationMetadata optimization_metadata; @@ -1819,8 +1839,8 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( GURL("https://somedomain.org/nomatch")); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); hints_manager()->RegisterOptimizationTypes( @@ -1911,8 +1931,8 @@ ProcessHints(config, "1.0.0.0"); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); optimization_guide::OptimizationTypeDecision optimization_type_decision = @@ -1957,8 +1977,8 @@ ProcessHints(config, "1.0.0.0"); base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); optimization_guide::OptimizationTypeDecision optimization_type_decision = @@ -2044,8 +2064,8 @@ url_with_hints()); // Wait for hint to be loaded. base::RunLoop run_loop; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run(); hints_manager()->CanApplyOptimizationAsync( @@ -2614,8 +2634,7 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_without_hints()); base::HistogramTester histogram_tester; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); histogram_tester.ExpectTotalCount( @@ -2668,8 +2687,7 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_without_hints()); base::HistogramTester histogram_tester; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); histogram_tester.ExpectUniqueSample( @@ -2699,8 +2717,7 @@ hints_manager()->SetHintsFetcherFactoryForTesting( BuildTestHintsFetcherFactory({HintsFetcherEndState::kFetchFailed})); base::HistogramTester histogram_tester; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); histogram_tester.ExpectTotalCount( @@ -2731,8 +2748,7 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); base::HistogramTester histogram_tester; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); histogram_tester.ExpectTotalCount( "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 0); @@ -2763,8 +2779,7 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); histogram_tester.ExpectTotalCount( @@ -2772,8 +2787,7 @@ // Make sure navigation data is populated correctly. OptimizationGuideNavigationData* navigation_data = - OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle( - navigation_handle.get()); + GetNavigationDataFromNavigationHandle(navigation_handle.get()); EXPECT_TRUE(navigation_data->hints_fetch_latency().has_value()); EXPECT_EQ(navigation_data->hints_fetch_attempt_status(), optimization_guide::RaceNavigationFetchAttemptStatus:: @@ -2796,8 +2810,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_hints()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); histogram_tester.ExpectBucketCount( @@ -2808,8 +2821,7 @@ // Make sure navigation data is populated correctly. OptimizationGuideNavigationData* navigation_data = - OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle( - navigation_handle.get()); + GetNavigationDataFromNavigationHandle(navigation_handle.get()); EXPECT_FALSE(navigation_data->hints_fetch_latency().has_value()); EXPECT_EQ(navigation_data->hints_fetch_attempt_status(), optimization_guide::RaceNavigationFetchAttemptStatus:: @@ -2841,16 +2853,14 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_without_hints()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); histogram_tester.ExpectTotalCount( "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 0); // Make sure navigation data is populated correctly. OptimizationGuideNavigationData* navigation_data = - OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle( - navigation_handle.get()); + GetNavigationDataFromNavigationHandle(navigation_handle.get()); EXPECT_TRUE(navigation_data->hints_fetch_latency().has_value()); EXPECT_EQ(navigation_data->hints_fetch_attempt_status(), optimization_guide::RaceNavigationFetchAttemptStatus:: @@ -2872,8 +2882,7 @@ navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_without_hints()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); histogram_tester.ExpectBucketCount( @@ -2882,8 +2891,7 @@ kRaceNavigationFetchHost, 1); OptimizationGuideNavigationData* navigation_data = - OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle( - navigation_handle.get()); + GetNavigationDataFromNavigationHandle(navigation_handle.get()); EXPECT_TRUE(navigation_data->hints_fetch_latency().has_value()); EXPECT_EQ(navigation_data->hints_fetch_attempt_status(), optimization_guide::RaceNavigationFetchAttemptStatus:: @@ -2904,8 +2912,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_without_hints()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); optimization_guide::OptimizationTypeDecision optimization_type_decision = hints_manager()->CanApplyOptimization( navigation_handle->GetURL(), /*navigation_id=*/absl::nullopt, @@ -2934,8 +2941,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_without_hints()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); optimization_guide::OptimizationTypeDecision optimization_type_decision = @@ -2964,8 +2970,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_without_hints()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); optimization_guide::OptimizationTypeDecision optimization_type_decision = @@ -2996,8 +3001,7 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_url_keyed_hint()); // Make sure URL-keyed hint is fetched and processed. - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); optimization_guide::OptimizationMetadata optimization_metadata; @@ -3033,8 +3037,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_url_keyed_hint()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); optimization_guide::OptimizationMetadata optimization_metadata; @@ -3066,8 +3069,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_url_keyed_hint()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); optimization_guide::OptimizationMetadata optimization_metadata; @@ -3100,8 +3102,7 @@ url_without_hints()); // Attempt to fetch a hint but ensure nothing comes back. - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); optimization_guide::OptimizationMetadata optimization_metadata; @@ -3135,8 +3136,7 @@ hints_manager()->SetHintsFetcherFactoryForTesting( BuildTestHintsFetcherFactory( {HintsFetcherEndState::kFetchSuccessWithHostHints})); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); optimization_guide::OptimizationMetadata optimization_metadata; optimization_guide::OptimizationTypeDecision optimization_type_decision = hints_manager()->CanApplyOptimization( @@ -3171,8 +3171,7 @@ hints_manager()->SetHintsFetcherFactoryForTesting( BuildTestHintsFetcherFactory( {HintsFetcherEndState::kFetchSuccessWithHostHints})); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); histogram_tester.ExpectUniqueSample( "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus", optimization_guide::RaceNavigationFetchAttemptStatus:: @@ -3183,17 +3182,11 @@ } { - ON_CALL(*navigation_handle, NavigationStart) - .WillByDefault(Return(base::TimeTicks::Now())); - - EXPECT_CALL(*navigation_handle, NavigationStart); - base::HistogramTester histogram_tester; hints_manager()->SetHintsFetcherFactoryForTesting( BuildTestHintsFetcherFactory( {HintsFetcherEndState::kFetchSuccessWithHostHints})); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); histogram_tester.ExpectUniqueSample( "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus", optimization_guide::RaceNavigationFetchAttemptStatus:: @@ -3204,8 +3197,7 @@ "OptimizationGuide.HintsManager.ConcurrentPageNavigationFetches", 0); OptimizationGuideNavigationData* navigation_data = - OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle( - navigation_handle.get()); + GetNavigationDataFromNavigationHandle(navigation_handle.get()); // Set hints fetch end.so we can figure out if hints fetch start was set. navigation_data->set_hints_fetch_end(base::TimeTicks::Now()); EXPECT_TRUE(navigation_data->hints_fetch_latency().has_value()); @@ -3237,8 +3229,7 @@ url_without_hints()); { base::HistogramTester histogram_tester; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); histogram_tester.ExpectUniqueSample( "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus", optimization_guide::RaceNavigationFetchAttemptStatus:: @@ -3253,8 +3244,7 @@ { base::HistogramTester histogram_tester; - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); histogram_tester.ExpectUniqueSample( "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus", optimization_guide::RaceNavigationFetchAttemptStatus:: @@ -3295,12 +3285,12 @@ // Attempt to fetch a hint but initiate the next navigations right away to // simulate being mid-fetch. base::HistogramTester histogram_tester; - hints_manager()->OnNavigationStartOrRedirect( - navigation_handle_with_hints.get(), base::DoNothing()); - hints_manager()->OnNavigationStartOrRedirect( - navigation_handle_without_hints.get(), base::DoNothing()); - hints_manager()->OnNavigationStartOrRedirect( - navigation_handle_without_hints2.get(), base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle_with_hints.get(), + base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle_without_hints.get(), + base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle_without_hints2.get(), + base::DoNothing()); // The third one is over the max and should evict another one. histogram_tester.ExpectTotalCount( @@ -3330,8 +3320,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_url_keyed_hint()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); hints_manager()->CanApplyOptimizationAsync( url_with_url_keyed_hint(), navigation_handle->GetNavigationId(), optimization_guide::proto::COMPRESS_PUBLIC_IMAGES, @@ -3388,8 +3377,7 @@ decision); EXPECT_TRUE(metadata.public_image_metadata().has_value()); })); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); histogram_tester.ExpectUniqueSample( @@ -3417,8 +3405,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_url_keyed_hint()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); hints_manager()->CanApplyOptimizationAsync( url_with_url_keyed_hint(), navigation_handle->GetNavigationId(), optimization_guide::proto::RESOURCE_LOADING, @@ -3463,8 +3450,7 @@ EXPECT_EQ(optimization_guide::OptimizationGuideDecision::kFalse, decision); })); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); histogram_tester.ExpectUniqueSample( @@ -3492,8 +3478,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_url_keyed_hint()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); hints_manager()->CanApplyOptimizationAsync( @@ -3533,8 +3518,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_url_keyed_hint()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); hints_manager()->CanApplyOptimizationAsync( @@ -3573,8 +3557,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_without_hints()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); hints_manager()->CanApplyOptimizationAsync( url_without_hints(), navigation_handle->GetNavigationId(), optimization_guide::proto::PERFORMANCE_HINTS, @@ -3647,8 +3630,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_url_keyed_hint()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); hints_manager()->CanApplyOptimizationAsync( url_with_url_keyed_hint(), navigation_handle->GetNavigationId(), optimization_guide::proto::COMPRESS_PUBLIC_IMAGES, @@ -3762,8 +3744,7 @@ std::unique_ptr<content::MockNavigationHandle> navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver( url_with_url_keyed_hint()); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); hints_manager()->CanApplyOptimizationAsync( url_with_url_keyed_hint(), navigation_handle->GetNavigationId(), optimization_guide::proto::COMPRESS_PUBLIC_IMAGES, @@ -3817,8 +3798,7 @@ CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(url); // Attempt to fetch a hint but ensure nothing comes back. - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - base::DoNothing()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing()); RunUntilIdle(); optimization_guide::OptimizationMetadata optimization_metadata; @@ -3847,8 +3827,8 @@ navigation_handle = CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(url); - hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(), - run_loop.QuitClosure()); + CallOnNavigationStartOrRedirect(navigation_handle.get(), + run_loop.QuitClosure()); run_loop.Run();
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc index ba9cebc..afe0628 100644 --- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc +++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
@@ -17,7 +17,6 @@ #include "chrome/browser/metrics/chrome_metrics_service_accessor.h" #include "chrome/browser/optimization_guide/optimization_guide_hints_manager.h" #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" -#include "chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h" #include "chrome/browser/optimization_guide/prediction/prediction_manager.h" #include "chrome/browser/profiles/profile.h" #include "components/leveldb_proto/public/proto_database_provider.h" @@ -216,16 +215,14 @@ } void OptimizationGuideKeyedService::OnNavigationStartOrRedirect( - content::NavigationHandle* navigation_handle) { + OptimizationGuideNavigationData* navigation_data) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - OptimizationGuideNavigationData* navigation_data = - GetNavigationDataFromNavigationHandle(navigation_handle); base::flat_set<optimization_guide::proto::OptimizationType> registered_optimization_types = hints_manager_->registered_optimization_types(); if (!registered_optimization_types.empty()) { - hints_manager_->OnNavigationStartOrRedirect(navigation_handle, + hints_manager_->OnNavigationStartOrRedirect(navigation_data, base::DoNothing()); } @@ -259,20 +256,6 @@ optimization_targets_and_metadata); } -// static -OptimizationGuideNavigationData* -OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle( - content::NavigationHandle* navigation_handle) { - OptimizationGuideWebContentsObserver* - optimization_guide_web_contents_observer = - OptimizationGuideWebContentsObserver::FromWebContents( - navigation_handle->GetWebContents()); - if (!optimization_guide_web_contents_observer) - return nullptr; - return optimization_guide_web_contents_observer - ->GetOrCreateOptimizationGuideNavigationData(navigation_handle); -} - void OptimizationGuideKeyedService::ShouldTargetNavigationAsync( content::NavigationHandle* navigation_handle, optimization_guide::proto::OptimizationTarget optimization_target,
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h index b4127eb..b92605c 100644 --- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h +++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
@@ -100,11 +100,6 @@ optimization_guide::proto::OptimizationTarget optimization_target, std::unique_ptr<optimization_guide::ModelInfo> model_info); - // Returns the OptimizationGuideNavigationData for |navigation_handle|. Will - // return nullptr if one cannot be created for it for any reason. - static OptimizationGuideNavigationData* GetNavigationDataFromNavigationHandle( - content::NavigationHandle* navigation_handle); - private: friend class ChromeBrowsingDataRemoverDelegate; friend class HintsFetcherBrowserTest; @@ -131,9 +126,9 @@ } // Notifies |hints_manager_| that the navigation associated with - // |navigation_handle| has started or redirected. + // |navigation_data| has started or redirected. void OnNavigationStartOrRedirect( - content::NavigationHandle* navigation_handle); + OptimizationGuideNavigationData* navigation_data); // Notifies |hints_manager_| that the navigation associated with // |navigation_redirect_chain| has finished.
diff --git a/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc b/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc index 7fe5ec5..ea1fc4bf 100644 --- a/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc +++ b/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc
@@ -47,13 +47,15 @@ GetOrCreateOptimizationGuideNavigationData( content::NavigationHandle* navigation_handle) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK_EQ(web_contents(), navigation_handle->GetWebContents()); int64_t navigation_id = navigation_handle->GetNavigationId(); if (inflight_optimization_guide_navigation_datas_.find(navigation_id) == inflight_optimization_guide_navigation_datas_.end()) { // We do not have one already - create one. inflight_optimization_guide_navigation_datas_[navigation_id] = - std::make_unique<OptimizationGuideNavigationData>(navigation_id); + std::make_unique<OptimizationGuideNavigationData>( + navigation_id, navigation_handle->NavigationStart()); } DCHECK(inflight_optimization_guide_navigation_datas_.find(navigation_id) != @@ -80,8 +82,11 @@ if (!optimization_guide_keyed_service_) return; + OptimizationGuideNavigationData* navigation_data = + GetOrCreateOptimizationGuideNavigationData(navigation_handle); + navigation_data->set_navigation_url(navigation_handle->GetURL()); optimization_guide_keyed_service_->OnNavigationStartOrRedirect( - navigation_handle); + navigation_data); } void OptimizationGuideWebContentsObserver::DidRedirectNavigation( @@ -94,8 +99,11 @@ if (!optimization_guide_keyed_service_) return; + OptimizationGuideNavigationData* navigation_data = + GetOrCreateOptimizationGuideNavigationData(navigation_handle); + navigation_data->set_navigation_url(navigation_handle->GetURL()); optimization_guide_keyed_service_->OnNavigationStartOrRedirect( - navigation_handle); + navigation_data); } void OptimizationGuideWebContentsObserver::ClearHintsToFetchBasedOnPredictions(
diff --git a/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h b/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h index 7941f8e..b94b4bb 100644 --- a/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h +++ b/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h
@@ -31,12 +31,6 @@ public: ~OptimizationGuideWebContentsObserver() override; - // Gets the OptimizationGuideNavigationData associated with - // |navigation_handle|. If one does not exist already, one will be created for - // it. - OptimizationGuideNavigationData* GetOrCreateOptimizationGuideNavigationData( - content::NavigationHandle* navigation_handle); - // Notifies |this| to flush |last_navigation_data| so metrics are recorded. void FlushLastNavigationData(); @@ -48,6 +42,7 @@ private: friend class OptimizationGuideHintsManagerFetchingTest; + friend class OptimizationGuideHintsManagerTest; friend class content::WebContentsUserData< OptimizationGuideWebContentsObserver>; @@ -55,6 +50,12 @@ explicit OptimizationGuideWebContentsObserver( content::WebContents* web_contents); + // Gets the OptimizationGuideNavigationData associated with the + // |navigation_handle|. If one does not exist already, one will be created for + // it. + OptimizationGuideNavigationData* GetOrCreateOptimizationGuideNavigationData( + content::NavigationHandle* navigation_handle); + // Clears the state related to hints to be fetched at onload due to navigation // predictions. void ClearHintsToFetchBasedOnPredictions(
diff --git a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc index 5e211bf4..aad70d89 100644 --- a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc +++ b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
@@ -112,8 +112,10 @@ mock_translate_metrics_logger_->LogUIInteraction(ui_interaction); } - translate::TranslationType GetNextManualTranslationType() override { - return mock_translate_metrics_logger_->GetNextManualTranslationType(); + translate::TranslationType GetNextManualTranslationType( + bool is_context_menu_initiated_translation) override { + return mock_translate_metrics_logger_->GetNextManualTranslationType( + is_context_menu_initiated_translation); } void SetHasHrefTranslateTarget(bool has_href_translate_target) override {
diff --git a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewTest.java b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewTest.java index f783085..f783503 100644 --- a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewTest.java +++ b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewTest.java
@@ -163,7 +163,9 @@ showAndWaitForInflation(startupPaintPreview, tabbedPaintPreview, dismissCallback); assertAttachedAndShown(tabbedPaintPreview, true, true); // Should be removed on PageLoadFinished signal. - startupPaintPreview.getTabObserverForTesting().onPageLoadFinished(tab, null); + TestThreadUtils.runOnUiThreadBlocking(() -> { + startupPaintPreview.getTabObserverForTesting().onPageLoadFinished(tab, null); + }); assertAttachedAndShown(tabbedPaintPreview, false, false); Assert.assertEquals( "Dismiss callback should have been called.", 1, dismissCallback.getCallCount());
diff --git a/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckIntegrationTest.java b/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckIntegrationTest.java index 427a064..5a483b7 100644 --- a/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckIntegrationTest.java +++ b/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckIntegrationTest.java
@@ -28,6 +28,7 @@ import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.util.browser.Features; import org.chromium.components.browser_ui.settings.SettingsLauncher; +import org.chromium.content_public.browser.test.util.TestThreadUtils; /** * Integration test for the Password Check component, testing the interaction between sub-components @@ -59,7 +60,8 @@ @Test @MediumTest public void testDestroysComponentIfFirstInSettingsStack() { - PasswordCheckFactory.getOrCreate(mMockSettingsLauncher); + TestThreadUtils.runOnUiThreadBlockingNoException( + () -> PasswordCheckFactory.getOrCreate(mMockSettingsLauncher)); Activity activity = setUpUiLaunchedFromDialog(); activity.finish(); CriteriaHelper.pollUiThread(() -> activity.isDestroyed()); @@ -69,13 +71,14 @@ @Test @MediumTest public void testDoesNotDestroyComponentIfNotFirstInSettingsStack() { - PasswordCheckFactory.getOrCreate(mMockSettingsLauncher); + TestThreadUtils.runOnUiThreadBlockingNoException( + () -> PasswordCheckFactory.getOrCreate(mMockSettingsLauncher)); Activity activity = setUpUiLaunchedFromSettings(); activity.finish(); CriteriaHelper.pollUiThread(() -> activity.isDestroyed()); assertNotNull(PasswordCheckFactory.getPasswordCheckInstance()); // Clean up the password check component. - PasswordCheckFactory.destroy(); + TestThreadUtils.runOnUiThreadBlocking(PasswordCheckFactory::destroy); } private Activity setUpUiLaunchedFromSettings() {
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/options.html b/chrome/browser/resources/chromeos/accessibility/select_to_speak/options.html index 7dfe0cf..0f04a8a 100644 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/options.html +++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/options.html
@@ -60,7 +60,8 @@ <span class="i18n" msgid="options_natural_voices_explanation"> </span> <!-- TODO(https://crbug.com/1227547): Change to correct link --> - <a href="https://support.google.com/chromebook/?p=accessibility"> + <a target="_blank" rel="noreferrer" + href="https://support.google.com/chromebook/?p=accessibility"> <span class="i18n" msgid="options_natural_voices_explanation_more"> </span>
diff --git a/chrome/browser/resources/chromeos/audio/BUILD.gn b/chrome/browser/resources/chromeos/audio/BUILD.gn index 952a4e7a0..8144397 100644 --- a/chrome/browser/resources/chromeos/audio/BUILD.gn +++ b/chrome/browser/resources/chromeos/audio/BUILD.gn
@@ -6,11 +6,34 @@ import("//tools/typescript/ts_library.gni") import("//ui/webui/resources/tools/generate_grd.gni") +copy("copy_audio") { + sources = [ + "audio.ts", + "audio_broker.ts", + ] + outputs = [ "$target_gen_dir/{{source_file_part}}" ] +} + +copy("copy_mojo") { + deps = [ "//chrome/browser/ui/webui/chromeos/audio:mojo_bindings_webui_js" ] + sources = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/chromeos/audio/audio.mojom-webui.js" ] + outputs = [ "$target_gen_dir/{{source_file_part}}" ] +} + ts_library("build_ts") { deps = [ "//third_party/polymer/v3_0:library" ] - root_dir = "./" + extra_deps = [ + ":copy_audio", + ":copy_mojo", + ] + root_dir = "$target_gen_dir" out_dir = "$target_gen_dir/tsc" - in_files = [ "audio.ts" ] + tsconfig_base = "tsconfig_base.json" + in_files = [ + "audio_broker.ts", + "audio.ts", + "audio.mojom-webui.js", + ] } resources_grd_file = "$target_gen_dir/resources.grd"
diff --git a/chrome/browser/resources/chromeos/audio/audio.ts b/chrome/browser/resources/chromeos/audio/audio.ts index 2443d9f..7423680 100644 --- a/chrome/browser/resources/chromeos/audio/audio.ts +++ b/chrome/browser/resources/chromeos/audio/audio.ts
@@ -2,7 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import {AudioBroker} from './audio_broker.js'; + function initialize() { + const handler = AudioBroker.getInstance().handler; + handler.getAudioDeviceInfo().then(({deviceName}) => { + console.log('mock device name output: ' + deviceName); + }); console.log('welcome to the audio page.'); }
diff --git a/chrome/browser/resources/chromeos/audio/audio_broker.ts b/chrome/browser/resources/chromeos/audio/audio_broker.ts new file mode 100644 index 0000000..92361a5 --- /dev/null +++ b/chrome/browser/resources/chromeos/audio/audio_broker.ts
@@ -0,0 +1,27 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {PageCallbackRouter, PageHandlerFactory, PageHandlerRemote} from './audio.mojom-webui.js'; + +export class AudioBroker { + callbackRouter: PageCallbackRouter; + handler: PageHandlerRemote; + + constructor() { + this.callbackRouter = new PageCallbackRouter(); + + this.handler = new PageHandlerRemote(); + + const factory = PageHandlerFactory.getRemote(); + factory.createPageHandler( + this.callbackRouter.$.bindNewPipeAndPassRemote(), + this.handler.$.bindNewPipeAndPassReceiver()); + } + + static getInstance() { + return instance || (instance = new AudioBroker()); + } +} + +let instance: AudioBroker|null = null;
diff --git a/chrome/browser/resources/chromeos/audio/tsconfig_base.json b/chrome/browser/resources/chromeos/audio/tsconfig_base.json new file mode 100644 index 0000000..b98fa52 --- /dev/null +++ b/chrome/browser/resources/chromeos/audio/tsconfig_base.json
@@ -0,0 +1,6 @@ +{ + "extends": "../../../../../tools/typescript/tsconfig_base.json", + "compilerOptions": { + "allowJs": true + } +} \ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_search.html b/chrome/browser/resources/chromeos/emoji_picker/emoji_search.html index 80769858..78e2b3c 100644 --- a/chrome/browser/resources/chromeos/emoji_picker/emoji_search.html +++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_search.html
@@ -98,6 +98,10 @@ overflow-y: scroll; padding-bottom: var(--emoji-picker-bottom-padding); } + + #no-emoji-image { + display: block; + } </style> <div id="search-shadow"> @@ -120,7 +124,7 @@ </template> <template is="dom-if" if="[[!results.length]]"> <div class="no-result"> - <img src="no_results.svg"> + <img src="no_results.svg" id = "no-emoji-image"> No emoji found </div> </template>
diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resources/settings/BUILD.gn index 55909d5..e80c5f2 100644 --- a/chrome/browser/resources/settings/BUILD.gn +++ b/chrome/browser/resources/settings/BUILD.gn
@@ -146,7 +146,7 @@ "route.js", "router.js", "safety_check_page/safety_check_browser_proxy.js", - "search_engines_page/search_engines_browser_proxy.js", + "search_engines_page/search_engines_browser_proxy.ts", "search_settings.js", "setting_id_param_util.ts", "settings.ts", @@ -273,13 +273,13 @@ "safety_check_page/safety_check_passwords_child.js", "safety_check_page/safety_check_safe_browsing_child.js", "safety_check_page/safety_check_updates_child.js", - "search_engines_page/omnibox_extension_entry.js", - "search_engines_page/search_engine_dialog.js", - "search_engines_page/search_engine_entry_css.js", - "search_engines_page/search_engine_entry.js", - "search_engines_page/search_engines_list.js", - "search_engines_page/search_engines_page.js", - "search_page/search_page.js", + "search_engines_page/omnibox_extension_entry.ts", + "search_engines_page/search_engine_dialog.ts", + "search_engines_page/search_engine_entry_css.ts", + "search_engines_page/search_engine_entry.ts", + "search_engines_page/search_engines_list.ts", + "search_engines_page/search_engines_page.ts", + "search_page/search_page.ts", "settings_main/settings_main.js", "settings_page/settings_animated_pages.js", "settings_page/settings_section.js", @@ -383,8 +383,6 @@ "privacy_page/privacy_review:closure_compile", "privacy_sandbox:closure_compile", "safety_check_page:closure_compile", - "search_engines_page:closure_compile", - "search_page:closure_compile", "settings_main:closure_compile", "settings_menu:closure_compile", "settings_page:closure_compile", @@ -455,7 +453,6 @@ "privacy_page:privacy_page", "privacy_page:privacy_page_browser_proxy", "safety_check_page:safety_check_browser_proxy", - "search_engines_page:search_engines_browser_proxy", "settings_ui:settings_ui", ] extra_deps = [ ":build_ts" ] @@ -779,14 +776,14 @@ "safety_check_page/safety_check_passwords_child.js", "safety_check_page/safety_check_safe_browsing_child.js", "safety_check_page/safety_check_updates_child.js", - "search_engines_page/omnibox_extension_entry.js", - "search_engines_page/search_engine_dialog.js", - "search_engines_page/search_engine_entry_css.js", - "search_engines_page/search_engine_entry.js", - "search_engines_page/search_engines_browser_proxy.js", - "search_engines_page/search_engines_list.js", - "search_engines_page/search_engines_page.js", - "search_page/search_page.js", + "search_engines_page/omnibox_extension_entry.ts", + "search_engines_page/search_engine_dialog.ts", + "search_engines_page/search_engine_entry_css.ts", + "search_engines_page/search_engine_entry.ts", + "search_engines_page/search_engines_browser_proxy.ts", + "search_engines_page/search_engines_list.ts", + "search_engines_page/search_engines_page.ts", + "search_page/search_page.ts", "search_settings.js", "setting_id_param_util.ts", "settings.ts", @@ -891,7 +888,10 @@ [ "//ui/webui/resources/cr_components/certificate_manager:build_ts" ] } - definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ] + definitions = [ + "//tools/typescript/definitions/chrome_send.d.ts", + "//tools/typescript/definitions/settings_private.d.ts", + ] extra_deps = [ ":preprocess",
diff --git a/chrome/browser/resources/settings/basic_page/BUILD.gn b/chrome/browser/resources/settings/basic_page/BUILD.gn index 7a463f9..5d6159f7 100644 --- a/chrome/browser/resources/settings/basic_page/BUILD.gn +++ b/chrome/browser/resources/settings/basic_page/BUILD.gn
@@ -22,7 +22,6 @@ "../prefs:prefs_behavior", "../privacy_page:privacy_page", "../safety_check_page:safety_check_page", - "../search_page", "../settings_page:main_page_mixin", "//ui/webui/resources/js:load_time_data.m", ]
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn index b6b918b..5d5a5da9 100644 --- a/chrome/browser/resources/settings/chromeos/BUILD.gn +++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -238,6 +238,7 @@ "chromeos/os_printing_page/cups_printers_entry_manager.js", "chromeos/os_search_page/search_engines_browser_proxy.js", "chromeos/bluetooth_page/bluetooth_page_browser_proxy.js", + "chromeos/os_about_page/device_name_browser_proxy.js", "chromeos/os_reset_page/os_reset_browser_proxy.js", "chromeos/os_settings.js", "chromeos/personalization_page/change_picture_browser_proxy.js", @@ -391,6 +392,8 @@ "chromeos/os_bluetooth_page/os_bluetooth_page.js", "chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js", "chromeos/os_bluetooth_page/os_bluetooth_summary.js", + "chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js", + "chromeos/os_bluetooth_page/os_paired_bluetooth_list.js", "chromeos/os_a11y_page/change_dictation_locale_dialog.js", "chromeos/os_a11y_page/manage_a11y_page.m.js", "chromeos/os_a11y_page/os_a11y_page.m.js", @@ -405,12 +408,11 @@ "chromeos/os_a11y_page/manage_a11y_page_browser_proxy.m.js", "chromeos/os_a11y_page/tts_subpage.m.js", "chromeos/os_a11y_page/tts_subpage_browser_proxy.m.js", - "chromeos/os_about_page/channel_switcher_dialog.m.js", - "chromeos/os_about_page/detailed_build_info.m.js", - "chromeos/os_about_page/device_name_browser_proxy.m.js", - "chromeos/os_about_page/edit_hostname_dialog.m.js", - "chromeos/os_about_page/os_about_page.m.js", - "chromeos/os_about_page/update_warning_dialog.m.js", + "chromeos/os_about_page/channel_switcher_dialog.js", + "chromeos/os_about_page/detailed_build_info.js", + "chromeos/os_about_page/edit_hostname_dialog.js", + "chromeos/os_about_page/os_about_page.js", + "chromeos/os_about_page/update_warning_dialog.js", "chromeos/os_apps_page/android_apps_browser_proxy.m.js", "chromeos/os_apps_page/android_apps_subpage.m.js", "chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.js", @@ -725,7 +727,7 @@ "nearby_share_page:polymer3_elements", "os_a11y_page:polymer3_elements", "os_a11y_page:web_components", - "os_about_page:polymer3_elements", + "os_about_page:web_components", "os_apps_page:polymer3_elements", "os_apps_page/app_management_page:polymer3_elements", "os_apps_page/app_management_page/borealis_page:polymer3_elements",
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn index b1b4c16..32c4417 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn +++ b/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn
@@ -3,39 +3,35 @@ # found in the LICENSE file. import("//third_party/closure_compiler/compile_js.gni") -import("//tools/polymer/polymer.gni") -import("//ui/webui/resources/tools/js_modulizer.gni") +import("//tools/polymer/html_to_js.gni") import("../os_settings.gni") js_type_check("closure_compile_module") { closure_flags = os_settings_closure_flags is_polymer3 = true deps = [ - ":channel_switcher_dialog.m", - ":detailed_build_info.m", - ":device_name_browser_proxy.m", - ":edit_hostname_dialog.m", - ":os_about_page.m", - ":update_warning_dialog.m", + ":channel_switcher_dialog", + ":detailed_build_info", + ":device_name_browser_proxy", + ":edit_hostname_dialog", + ":os_about_page", + ":update_warning_dialog", ] } -js_library("channel_switcher_dialog.m") { - sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.m.js" ] +js_library("channel_switcher_dialog") { deps = [ "../../about_page:about_page_browser_proxy", "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", "//ui/webui/resources/js:assert.m", "//ui/webui/resources/js:load_time_data.m", ] - extra_deps = [ ":channel_switcher_dialog_module" ] } -js_library("detailed_build_info.m") { - sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.m.js" ] +js_library("detailed_build_info") { deps = [ - ":channel_switcher_dialog.m", - ":edit_hostname_dialog.m", + ":channel_switcher_dialog", + ":edit_hostname_dialog", "..:deep_linking_behavior.m", "..:os_route.m", "../..:router", @@ -45,31 +41,25 @@ "//ui/webui/resources/cr_elements/policy:cr_tooltip_icon.m", "//ui/webui/resources/js:i18n_behavior.m", ] - extra_deps = [ ":detailed_build_info_module" ] } -js_library("device_name_browser_proxy.m") { - sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.m.js" ] +js_library("device_name_browser_proxy") { deps = [ "//ui/webui/resources/js:cr.m" ] externs_list = [ "$externs_path/chrome_send.js" ] - extra_deps = [ ":modulize" ] } -js_library("edit_hostname_dialog.m") { - sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.m.js" ] +js_library("edit_hostname_dialog") { deps = [ "../../about_page:about_page_browser_proxy", "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", "//ui/webui/resources/js:assert.m", "//ui/webui/resources/js:load_time_data.m", ] - extra_deps = [ ":edit_hostname_dialog_module" ] } -js_library("os_about_page.m") { - sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.m.js" ] +js_library("os_about_page") { deps = [ - ":device_name_browser_proxy.m", + ":device_name_browser_proxy", "..:deep_linking_behavior.m", "..:os_route.m", "../..:i18n_setup", @@ -83,75 +73,22 @@ "//ui/webui/resources/js:parse_html_subset.m", "//ui/webui/resources/js:web_ui_listener_behavior.m", ] - extra_deps = [ ":os_about_page_module" ] } -js_library("update_warning_dialog.m") { - sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.m.js" ] +js_library("update_warning_dialog") { deps = [ "../../about_page:about_page_browser_proxy", "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", "//ui/webui/resources/js:i18n_behavior.m", ] - extra_deps = [ ":update_warning_dialog_module" ] } -group("polymer3_elements") { - public_deps = [ - ":channel_switcher_dialog_module", - ":detailed_build_info_module", - ":edit_hostname_dialog_module", - ":modulize", - ":os_about_page_module", - ":update_warning_dialog_module", +html_to_js("web_components") { + js_files = [ + "channel_switcher_dialog.js", + "detailed_build_info.js", + "edit_hostname_dialog.js", + "os_about_page.js", + "update_warning_dialog.js", ] } - -polymer_modulizer("channel_switcher_dialog") { - js_file = "channel_switcher_dialog.js" - html_file = "channel_switcher_dialog.html" - html_type = "dom-module" - migrated_imports = os_settings_migrated_imports - namespace_rewrites = os_settings_namespace_rewrites - auto_imports = os_settings_auto_imports -} - -polymer_modulizer("detailed_build_info") { - js_file = "detailed_build_info.js" - html_file = "detailed_build_info.html" - html_type = "dom-module" - migrated_imports = os_settings_migrated_imports - namespace_rewrites = os_settings_namespace_rewrites - auto_imports = os_settings_auto_imports -} - -polymer_modulizer("edit_hostname_dialog") { - js_file = "edit_hostname_dialog.js" - html_file = "edit_hostname_dialog.html" - html_type = "dom-module" - migrated_imports = os_settings_migrated_imports - namespace_rewrites = os_settings_namespace_rewrites - auto_imports = os_settings_auto_imports -} - -polymer_modulizer("os_about_page") { - js_file = "os_about_page.js" - html_file = "os_about_page.html" - html_type = "dom-module" - migrated_imports = os_settings_migrated_imports - namespace_rewrites = os_settings_namespace_rewrites - auto_imports = os_settings_auto_imports -} - -polymer_modulizer("update_warning_dialog") { - js_file = "update_warning_dialog.js" - html_file = "update_warning_dialog.html" - html_type = "dom-module" - migrated_imports = os_settings_migrated_imports - namespace_rewrites = os_settings_namespace_rewrites - auto_imports = os_settings_auto_imports -} - -js_modulizer("modulize") { - input_files = [ "device_name_browser_proxy.js" ] -}
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.html b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.html index 36ccecd55..348b21d 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.html +++ b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.html
@@ -1,73 +1,56 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> - -<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-selector/iron-selector.html"> -<link rel="import" href="../../about_page/about_page_browser_proxy.html"> -<link rel="import" href="../../settings_shared_css.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/load_time_data.html"> - -<dom-module id="settings-channel-switcher-dialog"> - <template> - <style include="settings-shared"> - #warningSelector > :not(.iron-selected) { - display: none; - } - </style> - <cr-dialog id="dialog" close-text="$i18n{close}"> - <div slot="title">$i18n{aboutChangeChannel}</div> - <div slot="body"> - <!-- TODO(dbeam): this can be policy-controlled. Show this in the UI. - https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ChromeOsReleaseChannel - --> - <cr-radio-group on-selected-changed="onChannelSelectionChanged_"> - <cr-radio-button name="[[browserChannelEnum_.STABLE]]"> - $i18n{aboutChannelDialogStable} - </cr-radio-button> - <cr-radio-button name="[[browserChannelEnum_.BETA]]"> - $i18n{aboutChannelDialogBeta} - </cr-radio-button> - <cr-radio-button name="[[browserChannelEnum_.DEV]]"> - $i18n{aboutChannelDialogDev} - </cr-radio-button> - </cr-radio-group> - <iron-selector id="warningSelector"> - <div> - <h2>$i18n{aboutDelayedWarningTitle}</h2> - <div>[[substituteString_( - '$i18nPolymer{aboutDelayedWarningMessage}', - '$i18nPolymer{aboutOsProductTitle}')]]</div> - </div> - <div> - <h2>$i18n{aboutPowerwashWarningTitle}</h2> - <div>$i18n{aboutPowerwashWarningMessage}</div> - </div> - <div> - <h2>$i18n{aboutUnstableWarningTitle}</h2> - <div>[[substituteString_( - '$i18nPolymer{aboutUnstableWarningMessage}', - '$i18nPolymer{aboutOsProductTitle}')]]</div> - </div> - </iron-selector> +<style include="settings-shared"> + #warningSelector > :not(.iron-selected) { + display: none; + } +</style> +<cr-dialog id="dialog" close-text="$i18n{close}"> + <div slot="title">$i18n{aboutChangeChannel}</div> + <div slot="body"> + <!-- TODO(dbeam): this can be policy-controlled. Show this in the UI. + https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ChromeOsReleaseChannel + --> + <cr-radio-group on-selected-changed="onChannelSelectionChanged_"> + <cr-radio-button name="[[browserChannelEnum_.STABLE]]"> + $i18n{aboutChannelDialogStable} + </cr-radio-button> + <cr-radio-button name="[[browserChannelEnum_.BETA]]"> + $i18n{aboutChannelDialogBeta} + </cr-radio-button> + <cr-radio-button name="[[browserChannelEnum_.DEV]]"> + $i18n{aboutChannelDialogDev} + </cr-radio-button> + </cr-radio-group> + <iron-selector id="warningSelector"> + <div> + <h2>$i18n{aboutDelayedWarningTitle}</h2> + <div>[[substituteString_( + '$i18nPolymer{aboutDelayedWarningMessage}', + '$i18nPolymer{aboutOsProductTitle}')]]</div> </div> - <div slot="button-container"> - <cr-button class="cancel-button" on-click="onCancelTap_" - id="cancel">$i18n{cancel}</cr-button> - <cr-button id="changeChannel" class="action-button" - on-click="onChangeChannelTap_" - hidden="[[!shouldShowButtons_.changeChannel]]"> - $i18n{aboutChangeChannel} - </cr-button> - <cr-button id="changeChannelAndPowerwash" class="action-button" - on-click="onChangeChannelAndPowerwashTap_" - hidden="[[!shouldShowButtons_.changeChannelAndPowerwash]]"> - $i18n{aboutChangeChannelAndPowerwash} - </cr-button> + <div> + <h2>$i18n{aboutPowerwashWarningTitle}</h2> + <div>$i18n{aboutPowerwashWarningMessage}</div> </div> - </cr-dialog> - </template> - <script src="channel_switcher_dialog.js"></script> -</dom-module> + <div> + <h2>$i18n{aboutUnstableWarningTitle}</h2> + <div>[[substituteString_( + '$i18nPolymer{aboutUnstableWarningMessage}', + '$i18nPolymer{aboutOsProductTitle}')]]</div> + </div> + </iron-selector> + </div> + <div slot="button-container"> + <cr-button class="cancel-button" on-click="onCancelTap_" + id="cancel">$i18n{cancel}</cr-button> + <cr-button id="changeChannel" class="action-button" + on-click="onChangeChannelTap_" + hidden="[[!shouldShowButtons_.changeChannel]]"> + $i18n{aboutChangeChannel} + </cr-button> + <cr-button id="changeChannelAndPowerwash" class="action-button" + on-click="onChangeChannelAndPowerwashTap_" + hidden="[[!shouldShowButtons_.changeChannelAndPowerwash]]"> + $i18n{aboutChangeChannelAndPowerwash} + </cr-button> + </div> +</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js index e39e438..3e30edc5 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js +++ b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js
@@ -2,7 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -(function() { +import '//resources/cr_elements/cr_dialog/cr_dialog.m.js'; +import '//resources/cr_elements/cr_button/cr_button.m.js'; +import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js'; +import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js'; +import '//resources/polymer/v3_0/iron-selector/iron-selector.js'; +import '../../settings_shared_css.js'; + +import {assert, assertNotReached} from '//resources/js/assert.m.js'; +import {loadTimeData} from '//resources/js/load_time_data.m.js'; +import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, ChannelInfo, isTargetChannelMoreStable, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent, VersionInfo} from '../../about_page/about_page_browser_proxy.js'; + /** */ @@ -20,6 +32,7 @@ * release channel to notify parents of this dialog. */ Polymer({ + _template: html`{__html_template__}`, is: 'settings-channel-switcher-dialog', properties: { @@ -45,12 +58,12 @@ }, }, - /** @private {?settings.AboutPageBrowserProxy} */ + /** @private {?AboutPageBrowserProxy} */ browserProxy_: null, /** @override */ ready() { - this.browserProxy_ = settings.AboutPageBrowserProxyImpl.getInstance(); + this.browserProxy_ = AboutPageBrowserProxyImpl.getInstance(); this.browserProxy_.getChannelInfo().then(info => { this.currentChannel_ = info.currentChannel; this.targetChannel_ = info.targetChannel; @@ -125,8 +138,7 @@ return; } - if (settings.isTargetChannelMoreStable( - this.currentChannel_, selectedChannel)) { + if (isTargetChannelMoreStable(this.currentChannel_, selectedChannel)) { // More stable channel selected. For non managed devices, notify the user // about powerwash. if (loadTimeData.getBoolean('aboutEnterpriseManaged')) { @@ -157,4 +169,3 @@ return loadTimeData.substituteString(format, replacement); }, }); -})(); \ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.html b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.html index 8c1d26fe..70c8d24 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.html +++ b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.html
@@ -1,133 +1,108 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="settings-shared"> + cr-policy-indicator { + margin-inline-start: var(--cr-controlled-by-spacing); + } -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html"> -<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator_behavior.html"> -<link rel="import" href="chrome://resources/cr_elements/policy/cr_tooltip_icon.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html"> -<link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="../../about_page/about_page_browser_proxy.html"> -<link rel="import" href="../../i18n_setup.html"> -<link rel="import" href="../../router.html"> -<link rel="import" href="../../settings_shared_css.html"> -<link rel="import" href="../deep_linking_behavior.html"> -<link rel="import" href="../localized_link/localized_link.html"> -<link rel="import" href="../os_route.html"> -<link rel="import" href="channel_switcher_dialog.html"> -<link rel="import" href="device_name_browser_proxy.html"> -<link rel="import" href="edit_hostname_dialog.html"> + /* The command line string can contain very long substrings that + * don't have any spaces, need to force a line break in such cases. */ + #command-line { + overflow-wrap: break-word; + width: 100%; + } -<dom-module id="settings-detailed-build-info"> - <template> - <style include="settings-shared"> - cr-policy-indicator { - margin-inline-start: var(--cr-controlled-by-spacing); - } - - /* The command line string can contain very long substrings that - * don't have any spaces, need to force a line break in such cases. */ - #command-line { - overflow-wrap: break-word; - width: 100%; - } - - #managedEolTooltipIcon { - margin-inline-end: 54px; - } - </style> - <div class="settings-box two-line"> - <div class="start"> - <div role="heading" aria-level="2">$i18n{aboutChannelLabel}</div> - <div id="currentlyOnChannelText" aria-hidden="true" class="secondary"> - [[currentlyOnChannelText_]] - </div> - </div> - <div class="separator"></div> - <cr-button on-click="onChangeChannelTap_" - aria-describedby="currentlyOnChannelText" - disabled="[[!canChangeChannel_]]" - deep-link-focus-id$="[[Setting.kChangeChromeChannel]]"> - $i18n{aboutChangeChannel} - </cr-button> - <template is="dom-if" if="[[!canChangeChannel_]]"> - <cr-policy-indicator - indicator-source-name="[[getChangeChannelIndicatorSourceName_( - canChangeChannel_)]]" - indicator-type="[[getChangeChannelIndicatorType_( - canChangeChannel_)]]"> - </cr-policy-indicator> - </template> - <template is="dom-if" if="[[showChannelSwitcherDialog_]]" restamp> - <settings-channel-switcher-dialog - on-close="onChannelSwitcherDialogClosed_"> - </settings-channel-switcher-dialog> - </template> + #managedEolTooltipIcon { + margin-inline-end: 54px; + } +</style> +<div class="settings-box two-line"> + <div class="start"> + <div role="heading" aria-level="2">$i18n{aboutChannelLabel}</div> + <div id="currentlyOnChannelText" aria-hidden="true" class="secondary"> + [[currentlyOnChannelText_]] </div> - <div id="endOfLifeSectionContainer" - class="settings-box two-line single-column" - hidden="[[shouldHideEolInfo_]]"> - <div role="heading" aria-level="2">$i18n{aboutEndOfLifeTitle}</div> - <settings-localized-link class="secondary" - localized-string="[[eolMessageWithMonthAndYear]]"> - </settings-localized-link> - </div> - <template is="dom-if" if="[[isManaged_]]"> - <div class="settings-box two-line"> - <div class="start" aria-hidden="true"> - <div role="heading">$i18n{aboutEndOfLifeTitle}</div> - <div class="secondary"> - $i18n{aboutManagedEndOfLifeSubtitle} - </div> - </div> - <cr-tooltip-icon - id="managedEolTooltipIcon" - icon-class="cr20:domain" - tooltip-text="$i18n{aboutManagedEndOfLifeSubtitle}" - icon-aria-label="$i18n{aboutManagedEndOfLifeSubtitle}" - tooltip-position="bottom"> - </cr-tooltip-icon> - </div> - </template> - <template is="dom-if" if="[[isHostnameSettingEnabled_]]"> - <div class="settings-box two-line"> - <div class="start"> - <div role="heading">$i18n{aboutDeviceName}</div> - <div id="deviceName" aria-hidden="true" class="secondary"> - [[deviceNameText_]] - </div> - </div> - <cr-icon-button class="icon-edit" on-click="onEditHostnameTap_"> - </cr-icon-button> - </div> - <template is="dom-if" if="[[showEditHostnameDialog_]]" restamp> - <edit-hostname-dialog - on-close="onEditHostnameDialogClosed_"> - </edit-hostname-dialog> - </template> - </template> - <div id="buildDetailsLinkContainer" class="settings-box"> - <div class="start" id="aboutBuildDetailsTitle"> - $i18n{aboutBuildDetailsTitle} - </div> - <cr-icon-button id="copyBuildDetailsButton" class="icon-copy-content" - aria-labelledby="copyBuildDetailsButtonToolTip" - on-click="onCopyBuildDetailsToClipBoardTap_" - disabled="[[!copyToClipBoardEnabled_(versionInfo_, channelInfo_)]]" - deep-link-focus-id$="[[Setting.kCopyDetailedBuildInfo]]"> - </cr-icon-button> - <paper-tooltip id="copyBuildDetailsButtonToolTip" - for="copyBuildDetailsButton" - position="bottom" fit-to-visible-bounds> - $i18n{aboutBuildDetailsCopyTooltipLabel} - </paper-tooltip> - <div class="separator"></div> - <cr-icon-button on-click="onVisitBuildDetailsPageTap_" - aria-labelledby="aboutBuildDetailsTitle" - class="icon-external"> - </cr-icon-button> - </div> - <div class="hr"></div> + </div> + <div class="separator"></div> + <cr-button on-click="onChangeChannelTap_" + aria-describedby="currentlyOnChannelText" + disabled="[[!canChangeChannel_]]" + deep-link-focus-id$="[[Setting.kChangeChromeChannel]]"> + $i18n{aboutChangeChannel} + </cr-button> + <template is="dom-if" if="[[!canChangeChannel_]]"> + <cr-policy-indicator + indicator-source-name="[[getChangeChannelIndicatorSourceName_( + canChangeChannel_)]]" + indicator-type="[[getChangeChannelIndicatorType_( + canChangeChannel_)]]"> + </cr-policy-indicator> </template> - <script src="detailed_build_info.js"></script> -</dom-module> + <template is="dom-if" if="[[showChannelSwitcherDialog_]]" restamp> + <settings-channel-switcher-dialog + on-close="onChannelSwitcherDialogClosed_"> + </settings-channel-switcher-dialog> + </template> +</div> +<div id="endOfLifeSectionContainer" + class="settings-box two-line single-column" + hidden="[[shouldHideEolInfo_]]"> + <div role="heading" aria-level="2">$i18n{aboutEndOfLifeTitle}</div> + <settings-localized-link class="secondary" + localized-string="[[eolMessageWithMonthAndYear]]"> + </settings-localized-link> +</div> +<template is="dom-if" if="[[isManaged_]]"> + <div class="settings-box two-line"> + <div class="start" aria-hidden="true"> + <div role="heading">$i18n{aboutEndOfLifeTitle}</div> + <div class="secondary"> + $i18n{aboutManagedEndOfLifeSubtitle} + </div> + </div> + <cr-tooltip-icon + id="managedEolTooltipIcon" + icon-class="cr20:domain" + tooltip-text="$i18n{aboutManagedEndOfLifeSubtitle}" + icon-aria-label="$i18n{aboutManagedEndOfLifeSubtitle}" + tooltip-position="bottom"> + </cr-tooltip-icon> + </div> +</template> +<template is="dom-if" if="[[isHostnameSettingEnabled_]]"> + <div class="settings-box two-line"> + <div class="start"> + <div role="heading">$i18n{aboutDeviceName}</div> + <div id="deviceName" aria-hidden="true" class="secondary"> + [[deviceNameText_]] + </div> + </div> + <cr-icon-button class="icon-edit" on-click="onEditHostnameTap_"> + </cr-icon-button> + </div> + <template is="dom-if" if="[[showEditHostnameDialog_]]" restamp> + <edit-hostname-dialog + on-close="onEditHostnameDialogClosed_"> + </edit-hostname-dialog> + </template> +</template> +<div id="buildDetailsLinkContainer" class="settings-box"> + <div class="start" id="aboutBuildDetailsTitle"> + $i18n{aboutBuildDetailsTitle} + </div> + <cr-icon-button id="copyBuildDetailsButton" class="icon-copy-content" + aria-labelledby="copyBuildDetailsButtonToolTip" + on-click="onCopyBuildDetailsToClipBoardTap_" + disabled="[[!copyToClipBoardEnabled_(versionInfo_, channelInfo_)]]" + deep-link-focus-id$="[[Setting.kCopyDetailedBuildInfo]]"> + </cr-icon-button> + <paper-tooltip id="copyBuildDetailsButtonToolTip" + for="copyBuildDetailsButton" + position="bottom" fit-to-visible-bounds> + $i18n{aboutBuildDetailsCopyTooltipLabel} + </paper-tooltip> + <div class="separator"></div> + <cr-icon-button on-click="onVisitBuildDetailsPageTap_" + aria-labelledby="aboutBuildDetailsTitle" + class="icon-external"> + </cr-icon-button> +</div> +<div class="hr"></div>
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js index d77ddbd..3da862b 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js +++ b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js
@@ -7,13 +7,36 @@ * information for ChromeOS. */ +import '//resources/cr_elements/cr_button/cr_button.m.js'; +import '//resources/cr_elements/policy/cr_policy_indicator.m.js'; +import '//resources/cr_elements/policy/cr_tooltip_icon.m.js'; +import '../../settings_shared_css.js'; +import '../localized_link/localized_link.js'; +import './channel_switcher_dialog.js'; +import './edit_hostname_dialog.js'; + +import {CrPolicyIndicatorType} from '//resources/cr_elements/policy/cr_policy_indicator_behavior.m.js'; +import {assert, assertNotReached} from '//resources/js/assert.m.js'; +import {focusWithoutInk} from '//resources/js/cr/ui/focus_without_ink.m.js'; +import {I18nBehavior} from '//resources/js/i18n_behavior.m.js'; +import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, ChannelInfo, isTargetChannelMoreStable, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent, VersionInfo} from '../../about_page/about_page_browser_proxy.js'; +import {loadTimeData} from '../../i18n_setup.js'; +import {Route, RouteObserverBehavior, Router} from '../../router.js'; +import {DeepLinkingBehavior} from '../deep_linking_behavior.m.js'; +import {routes} from '../os_route.m.js'; + +import {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl} from './device_name_browser_proxy.js'; + Polymer({ + _template: html`{__html_template__}`, is: 'settings-detailed-build-info', behaviors: [ DeepLinkingBehavior, I18nBehavior, - settings.RouteObserverBehavior, + RouteObserverBehavior, ], properties: { @@ -86,7 +109,7 @@ /** @override */ ready() { - const browserProxy = settings.AboutPageBrowserProxyImpl.getInstance(); + const browserProxy = AboutPageBrowserProxyImpl.getInstance(); browserProxy.pageReady(); browserProxy.getVersionInfo().then(versionInfo => { @@ -101,12 +124,12 @@ }, /** - * @param {!settings.Route} route - * @param {!settings.Route} oldRoute + * @param {!Route} route + * @param {!Route} oldRoute */ currentRouteChanged(route, oldRoute) { // Does not apply to this page. - if (route !== settings.routes.DETAILED_BUILD_INFO) { + if (route !== routes.DETAILED_BUILD_INFO) { return; } @@ -123,7 +146,7 @@ /** @private */ updateChannelInfo_() { - const browserProxy = settings.AboutPageBrowserProxyImpl.getInstance(); + const browserProxy = AboutPageBrowserProxyImpl.getInstance(); // canChangeChannel() call is expected to be low-latency, so fetch this // value by itself to ensure UI consistency (see https://crbug.com/848750). @@ -138,8 +161,7 @@ // Display the target channel for the 'Currently on' message. this.currentlyOnChannelText_ = this.i18n( 'aboutCurrentlyOnChannel', - this.i18n( - settings.browserChannelToI18nId(info.targetChannel, info.isLts))); + this.i18n(browserChannelToI18nId(info.targetChannel, info.isLts))); }); }, @@ -238,14 +260,14 @@ /** @private */ onChannelSwitcherDialogClosed_() { this.showChannelSwitcherDialog_ = false; - cr.ui.focusWithoutInk(assert(this.$$('cr-button'))); + focusWithoutInk(assert(this.$$('cr-button'))); this.updateChannelInfo_(); }, /** @private */ onEditHostnameDialogClosed_() { this.showEditHostnameDialog_ = false; - cr.ui.focusWithoutInk(assert(this.$$('cr-button'))); + focusWithoutInk(assert(this.$$('cr-button'))); // TODO(jhawkins): Verify hostname property updated at this point. }, });
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js b/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js index df0e1f1..f8cfff17 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js +++ b/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js
@@ -3,7 +3,7 @@ // found in the LICENSE file. // clang-format off -// #import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js'; +import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js'; // clang-format on /** @@ -11,10 +11,10 @@ * deviceName: string, * }} */ -/* #export */ let DeviceNameMetadata; +export let DeviceNameMetadata; /** @interface */ -/* #export */ class DeviceNameBrowserProxy { +export class DeviceNameBrowserProxy { /** * Queries the system for metadata about the device name. * @return {!Promise<!DeviceNameMetadata>} @@ -25,13 +25,13 @@ /** * @implements {DeviceNameBrowserProxy} */ -/* #export */ class DeviceNameBrowserProxyImpl { +export class DeviceNameBrowserProxyImpl { /** @override */ getDeviceNameMetadata() { - return cr.sendWithPromise('getDeviceNameMetadata'); + return sendWithPromise('getDeviceNameMetadata'); } } // The singleton instance_ is replaced with a test version of this wrapper // during testing. -cr.addSingletonGetter(DeviceNameBrowserProxyImpl); +addSingletonGetter(DeviceNameBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.html b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.html index 8a64870..251350654 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.html +++ b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.html
@@ -1,50 +1,32 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="settings-shared"> + cr-input { + --cr-input-error-display: none; + } -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/load_time_data.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-selector/iron-selector.html"> -<link rel="import" href="../../about_page/about_page_browser_proxy.html"> -<link rel="import" href="../../settings_shared_css.html"> + #input-subtext { + display: flex; + } -<dom-module id="edit-hostname-dialog"> - <template> - <style include="settings-shared"> - cr-input { - --cr-input-error-display: none; - } - - #input-subtext { - display: flex; - } - - #input-subtext span:last-child { - margin-inline-start: auto; - } - </style> - <cr-dialog id="dialog" show-on-attach> - <div slot="title">$i18n{aboutEditDeviceName}</div> - <div slot="body"> - <div>$i18n{aboutDeviceNameInfo}</div> - <cr-input></cr-input> - <div id="input-subtext"> - <span>$i18n{aboutDeviceNameConstraints}</span> - <span>1/15</span> - </div> - </div> - <div slot="button-container"> - <cr-button class="cancel-button" on-click="onCancelTap_"> - $i18n{cancel} - </cr-button> - <cr-button class="action-button" on-click="onDoneTap_"> - $i18n{done} - </cr-button> - </div> - </cr-dialog> - </template> - <script src="edit_hostname_dialog.js"></script> -</dom-module> + #input-subtext span:last-child { + margin-inline-start: auto; + } +</style> +<cr-dialog id="dialog" show-on-attach> + <div slot="title">$i18n{aboutEditDeviceName}</div> + <div slot="body"> + <div>$i18n{aboutDeviceNameInfo}</div> + <cr-input></cr-input> + <div id="input-subtext"> + <span>$i18n{aboutDeviceNameConstraints}</span> + <span>1/15</span> + </div> + </div> + <div slot="button-container"> + <cr-button class="cancel-button" on-click="onCancelTap_"> + $i18n{cancel} + </cr-button> + <cr-button class="action-button" on-click="onDoneTap_"> + $i18n{done} + </cr-button> + </div> +</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js index fa76e09..d27796e0 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js +++ b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js
@@ -6,7 +6,22 @@ * @fileoverview 'edit-hostname-dialog' is a component allowing the * user to edit the device hostname. */ +import '//resources/cr_elements/cr_button/cr_button.m.js'; +import '//resources/cr_elements/cr_dialog/cr_dialog.m.js'; +import '//resources/cr_elements/cr_input/cr_input.m.js'; +import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js'; +import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js'; +import '//resources/polymer/v3_0/iron-selector/iron-selector.js'; +import '../../settings_shared_css.js'; + +import {assert, assertNotReached} from '//resources/js/assert.m.js'; +import {loadTimeData} from '//resources/js/load_time_data.m.js'; +import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, ChannelInfo, isTargetChannelMoreStable, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent, VersionInfo} from '../../about_page/about_page_browser_proxy.js'; + Polymer({ + _template: html`{__html_template__}`, is: 'edit-hostname-dialog', /** @private */
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html index ea71d9f..e29a1478 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html +++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html
@@ -1,260 +1,224 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> +<style include="settings-shared settings-page-styles"> + :host { + --about-page-image-space: 10px; + } -<link rel="import" href="chrome://resources/html/parse_html_subset.html"> -<link rel="import" href="../../about_page/about_page_browser_proxy.html"> -<link rel="import" href="../../i18n_setup.html"> -<link rel="import" href="../../icons.html"> -<link rel="import" href="../../lifetime_browser_proxy.html"> -<link rel="import" href="../../prefs/prefs.html"> -<link rel="import" href="../os_settings_page/main_page_behavior.html"> -<link rel="import" href="../../settings_page/settings_animated_pages.html"> -<link rel="import" href="../../settings_page/settings_section.html"> -<link rel="import" href="../../settings_page/settings_subpage.html"> -<link rel="import" href="../../settings_page_css.html"> -<link rel="import" href="../../settings_shared_css.html"> -<link rel="import" href="../deep_linking_behavior.html"> -<link rel="import" href="../metrics_recorder.html"> -<link rel="import" href="../os_icons.html"> -<link rel="import" href="../os_route.html"> -<link rel="import" href="../../router.html"> -<link rel="import" href="../os_reset_page/os_powerwash_dialog.html"> -<link rel="import" href="../localized_link/localized_link.html"> -<link rel="import" href="detailed_build_info.html"> -<link rel="import" href="update_warning_dialog.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html"> -<link rel="import" href="chrome://resources/cr_elements/icons.html"> -<link rel="import" href="chrome://resources/html/assert.html"> -<link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> + .info-section { + margin-bottom: 12px; + } -<dom-module id="os-settings-about-page"> - <template> - <style include="settings-shared settings-page-styles"> - :host { - --about-page-image-space: 10px; - } + .padded { + padding-bottom: 10px; + padding-top: 10px; + } - .info-section { - margin-bottom: 12px; - } + .product-title { + font-size: 153.85%; /* 20px / 13px */ + font-weight: 400; + margin-bottom: auto; + margin-top: auto; + } - .padded { - padding-bottom: 10px; - padding-top: 10px; - } + img { + margin-inline-end: var(--about-page-image-space); + } - .product-title { - font-size: 153.85%; /* 20px / 13px */ - font-weight: 400; - margin-bottom: auto; - margin-top: auto; - } + .icon-container { + margin-inline-end: var(--about-page-image-space); + min-width: 32px; /* The width of the product-logo img. */ + text-align: center; + } - img { - margin-inline-end: var(--about-page-image-space); - } + /* TODO(crbug.com/986596): Don't use browser icons here. Fork them. */ + iron-icon[icon='settings:check-circle'] { + fill: var(--cros-icon-color-prominent); + } - .icon-container { - margin-inline-end: var(--about-page-image-space); - min-width: 32px; /* The width of the product-logo img. */ - text-align: center; - } + iron-icon[icon='cr:error-outline'] { + fill: var(--cros-text-color-alert); + } - /* TODO(crbug.com/986596): Don't use browser icons here. Fork them. */ - iron-icon[icon='settings:check-circle'] { - fill: var(--cros-icon-color-prominent); - } + .settings-box .start { + overflow-x: auto; + } - iron-icon[icon='cr:error-outline'] { - fill: var(--cros-text-color-alert); - } + cr-button { + white-space: nowrap; + } - .settings-box .start { - overflow-x: auto; - } - - cr-button { - white-space: nowrap; - } - - #regulatoryInfo img { - width: 330px; - } - </style> - <settings-section page-title="$i18n{aboutOsPageTitle}" section="about"> - <settings-animated-pages id="pages" section="about" - focus-config="[[focusConfig_]]"> - <div route-path="default"> - <div class="settings-box two-line first"> - <img id="product-logo" on-click="onProductLogoClick_" - srcset="chrome://theme/current-channel-logo@1x 1x, - chrome://theme/current-channel-logo@2x 2x" - alt="$i18n{aboutProductLogoAlt}" - role="presentation"> - <h1 class="product-title">$i18n{aboutOsProductTitle}</h1> - </div> - <div class="settings-box two-line"> - <!-- Set the icon from the iconset (when it's obsolete/EOL and - when update is done) or set the src (when it's updating). --> - <div class="icon-container" - hidden="[[!shouldShowIcons_(showUpdateStatus_)]]"> - <!-- TODO(crbug.com/986596): Don't use browser icons here. Fork them. --> - <iron-icon - icon$="[[getUpdateStatusIcon_( - hasEndOfLife_, currentUpdateStatusEvent_)]]" - src="[[getThrobberSrcIfUpdating_( - hasEndOfLife_, currentUpdateStatusEvent_)]]"> - </iron-icon> - </div> - <div class="start padded"> - <div id="updateStatusMessage" hidden="[[!showUpdateStatus_]]"> - <div - inner-h-t-m-l="[[getUpdateStatusMessage_( - currentUpdateStatusEvent_, targetChannel_)]]"></div> - <a hidden$="[[!shouldShowLearnMoreLink_( - currentUpdateStatusEvent_)]]" target="_blank" - href="https://support.google.com/chrome?p=update_error"> - $i18n{learnMore} - </a> - </div> - <settings-localized-link - id="endOfLifeMessageContainer" hidden="[[!hasEndOfLife_]]" - localized-string="$i18n{endOfLifeMessage}"> - </settings-localized-link> - <div class="secondary">$i18n{aboutBrowserVersion}</div> - </div> - <div class="separator" hidden="[[!showButtonContainer_]]"></div> - <span id="buttonContainer" hidden="[[!showButtonContainer_]]"> - <cr-button id="relaunch" hidden$="[[!showRelaunch_]]" - on-click="onRelaunchClick_"> - [[getRelaunchButtonText_( - currentUpdateStatusEvent_)]] - </cr-button> - <cr-button id="checkForUpdates" hidden="[[!showCheckUpdates_]]" - on-click="onCheckUpdatesClick_" - deep-link-focus-id$="[[Setting.kCheckForOsUpdate]]"> - $i18n{aboutCheckForUpdates} - </cr-button> - </span> - </div> - <cr-link-row - id="aboutTPMFirmwareUpdate" - class="hr" - hidden$="[[!showTPMFirmwareUpdateLineItem_]]" - label="$i18n{aboutTPMFirmwareUpdateTitle}" - on-click="onTPMFirmwareUpdateClick_"> - <div slot="sub-label"> - $i18n{aboutTPMFirmwareUpdateDescription} - <a href="$i18n{aboutTPMFirmwareUpdateLearnMoreURL}" - target="_blank" on-click="onLearnMoreClick_"> - $i18n{learnMore} - </a> - </div> - </cr-link-row> - <template is="dom-if" if="[[hasInternetConnection_]]"> - <cr-link-row class="hr" id="releaseNotesOnline" - on-click="onReleaseNotesTap_" - label="$i18n{aboutShowReleaseNotes}" external - deep-link-focus-id$="[[Setting.kSeeWhatsNew]]"> - </cr-link-row> - </template> - <template is="dom-if" if="[[!hasInternetConnection_]]"> - <cr-link-row class="hr" id="releaseNotesOffline" - on-click="onReleaseNotesTap_" - label="$i18n{aboutShowReleaseNotes}" - title="$i18n{aboutReleaseNotesOffline}" external - deep-link-focus-id$="[[Setting.kSeeWhatsNew]]"> - </cr-link-row> - </template> - <cr-link-row class="hr" id="help" on-click="onHelpClick_" - label="$i18n{aboutGetHelpUsingChromeOs}" external - deep-link-focus-id$="[[Setting.kGetHelpWithChromeOs]]"> - </cr-link-row> -<if expr="_google_chrome"> - <cr-link-row class="hr" id="reportIssue" - on-click="onReportIssueClick_" - hidden="[[!prefs.feedback_allowed.value]]" - label="$i18n{aboutReportAnIssue}" external - deep-link-focus-id$="[[Setting.kReportAnIssue]]"> - </cr-link-row> -</if> - <cr-link-row class="hr" id="diagnostics" - on-click="onDiagnosticsClick_" - hidden$="[[!showDiagnosticsApp_]]" - label="$i18n{aboutDiagnostics}" external - deep-link-focus-id$="[[Setting.kDiagnostics]]"> - </cr-link-row> - <cr-link-row class="hr" id="detailed-build-info-trigger" - on-click="onDetailedBuildInfoClick_" - label="$i18n{aboutDetailedBuildInfo}" - role-description="$i18n{subpageArrowRoleDescription}"> - </cr-link-row> - <cr-link-row class="hr" on-click="onManagementPageClick_" - start-icon="cr:domain" label="$i18n{managementPage}" - hidden$="[[!isManaged_]]" external> - </cr-link-row> + #regulatoryInfo img { + width: 330px; + } +</style> +<settings-section page-title="$i18n{aboutOsPageTitle}" section="about"> + <settings-animated-pages id="pages" section="about" + focus-config="[[focusConfig_]]"> + <div route-path="default"> + <div class="settings-box two-line first"> + <img id="product-logo" on-click="onProductLogoClick_" + srcset="chrome://theme/current-channel-logo@1x 1x, + chrome://theme/current-channel-logo@2x 2x" + alt="$i18n{aboutProductLogoAlt}" + role="presentation"> + <h1 class="product-title">$i18n{aboutOsProductTitle}</h1> + </div> + <div class="settings-box two-line"> + <!-- Set the icon from the iconset (when it's obsolete/EOL and + when update is done) or set the src (when it's updating). --> + <div class="icon-container" + hidden="[[!shouldShowIcons_(showUpdateStatus_)]]"> + <!-- TODO(crbug.com/986596): Don't use browser icons here. Fork + them. --> + <iron-icon + icon$="[[getUpdateStatusIcon_( + hasEndOfLife_, currentUpdateStatusEvent_)]]" + src="[[getThrobberSrcIfUpdating_( + hasEndOfLife_, currentUpdateStatusEvent_)]]"> + </iron-icon> </div> - <template is="dom-if" route-path="/help/details"> - <settings-subpage page-title="$i18n{aboutDetailedBuildInfo}"> - <settings-detailed-build-info - eol-message-with-month-and-year= - "[[eolMessageWithMonthAndYear_]]"> - </settings-detailed-build-info> - </settings-subpage> - </template> - </settings-animated-pages> - </settings-section> - - <settings-section> - <div class="settings-box padded block first"> - <div class="info-section"> - <div class="secondary">$i18n{aboutOsProductTitle}</div> - <div class="secondary">$i18n{aboutProductCopyright}</div> - </div> - - <div class="info-section"> - <div class="secondary">$i18nRaw{aboutProductLicense}</div> - <div class="secondary" - inner-h-t-m-l="[[getAboutProductOsLicense_( - showCrostiniLicense_)]]"> + <div class="start padded"> + <div id="updateStatusMessage" hidden="[[!showUpdateStatus_]]"> + <div + inner-h-t-m-l="[[getUpdateStatusMessage_( + currentUpdateStatusEvent_, targetChannel_)]]"></div> + <a hidden$="[[!shouldShowLearnMoreLink_( + currentUpdateStatusEvent_)]]" target="_blank" + href="https://support.google.com/chrome?p=update_error"> + $i18n{learnMore} + </a> </div> + <settings-localized-link + id="endOfLifeMessageContainer" hidden="[[!hasEndOfLife_]]" + localized-string="$i18n{endOfLifeMessage}"> + </settings-localized-link> + <div class="secondary">$i18n{aboutBrowserVersion}</div> </div> -<if expr="_google_chrome"> - <div class="secondary"> - <a id="aboutProductTos" href="$i18n{aboutTermsURL}" target="_blank" - deep-link-focus-id$="[[Setting.kTermsOfService]]"> - $i18n{aboutProductTos} + <div class="separator" hidden="[[!showButtonContainer_]]"></div> + <span id="buttonContainer" hidden="[[!showButtonContainer_]]"> + <cr-button id="relaunch" hidden$="[[!showRelaunch_]]" + on-click="onRelaunchClick_"> + [[getRelaunchButtonText_( + currentUpdateStatusEvent_)]] + </cr-button> + <cr-button id="checkForUpdates" hidden="[[!showCheckUpdates_]]" + on-click="onCheckUpdatesClick_" + deep-link-focus-id$="[[Setting.kCheckForOsUpdate]]"> + $i18n{aboutCheckForUpdates} + </cr-button> + </span> + </div> + <cr-link-row + id="aboutTPMFirmwareUpdate" + class="hr" + hidden$="[[!showTPMFirmwareUpdateLineItem_]]" + label="$i18n{aboutTPMFirmwareUpdateTitle}" + on-click="onTPMFirmwareUpdateClick_"> + <div slot="sub-label"> + $i18n{aboutTPMFirmwareUpdateDescription} + <a href="$i18n{aboutTPMFirmwareUpdateLearnMoreURL}" + target="_blank" on-click="onLearnMoreClick_"> + $i18n{learnMore} </a> </div> -</if> - </div> - <div class="settings-box padded block" id="regulatoryInfo" - hidden$="[[!shouldShowRegulatoryOrSafetyInfo_(regulatoryInfo_)]]"> + </cr-link-row> + <template is="dom-if" if="[[hasInternetConnection_]]"> + <cr-link-row class="hr" id="releaseNotesOnline" + on-click="onReleaseNotesTap_" + label="$i18n{aboutShowReleaseNotes}" external + deep-link-focus-id$="[[Setting.kSeeWhatsNew]]"> + </cr-link-row> + </template> + <template is="dom-if" if="[[!hasInternetConnection_]]"> + <cr-link-row class="hr" id="releaseNotesOffline" + on-click="onReleaseNotesTap_" + label="$i18n{aboutShowReleaseNotes}" + title="$i18n{aboutReleaseNotesOffline}" external + deep-link-focus-id$="[[Setting.kSeeWhatsNew]]"> + </cr-link-row> + </template> + <cr-link-row class="hr" id="help" on-click="onHelpClick_" + label="$i18n{aboutGetHelpUsingChromeOs}" external + deep-link-focus-id$="[[Setting.kGetHelpWithChromeOs]]"> + </cr-link-row> <if expr="_google_chrome"> - <div class="secondary" hidden$="[[!shouldShowSafetyInfo_()]]"> - <a target="_blank" href="$i18n{aboutProductSafetyURL}"> - $i18nRaw{aboutProductSafety} - </a> - </div> + <cr-link-row class="hr" id="reportIssue" + on-click="onReportIssueClick_" + hidden="[[!prefs.feedback_allowed.value]]" + label="$i18n{aboutReportAnIssue}" external + deep-link-focus-id$="[[Setting.kReportAnIssue]]"> + </cr-link-row> </if> - <img src="[[regulatoryInfo_.url]]" alt="[[regulatoryInfo_.text]]" - hidden$="[[!shouldShowRegulatoryInfo_(regulatoryInfo_)]]"> + <cr-link-row class="hr" id="diagnostics" + on-click="onDiagnosticsClick_" + hidden$="[[!showDiagnosticsApp_]]" + label="$i18n{aboutDiagnostics}" external + deep-link-focus-id$="[[Setting.kDiagnostics]]"> + </cr-link-row> + <cr-link-row class="hr" id="detailed-build-info-trigger" + on-click="onDetailedBuildInfoClick_" + label="$i18n{aboutDetailedBuildInfo}" + role-description="$i18n{subpageArrowRoleDescription}"> + </cr-link-row> + <cr-link-row class="hr" on-click="onManagementPageClick_" + start-icon="cr:domain" label="$i18n{managementPage}" + hidden$="[[!isManaged_]]" external> + </cr-link-row> + </div> + <template is="dom-if" route-path="/help/details"> + <settings-subpage page-title="$i18n{aboutDetailedBuildInfo}"> + <settings-detailed-build-info + eol-message-with-month-and-year= + "[[eolMessageWithMonthAndYear_]]"> + </settings-detailed-build-info> + </settings-subpage> + </template> + </settings-animated-pages> +</settings-section> + +<settings-section> + <div class="settings-box padded block first"> + <div class="info-section"> + <div class="secondary">$i18n{aboutOsProductTitle}</div> + <div class="secondary">$i18n{aboutProductCopyright}</div> + </div> + + <div class="info-section"> + <div class="secondary">$i18nRaw{aboutProductLicense}</div> + <div class="secondary" + inner-h-t-m-l="[[getAboutProductOsLicense_( + showCrostiniLicense_)]]"> </div> - </settings-section> - <template is="dom-if" if="[[showUpdateWarningDialog_]]" restamp> - <settings-update-warning-dialog update-info="[[updateInfo_]]" - on-close="onUpdateWarningDialogClose_"> - </settings-update-warning-dialog> - </template> - <template is="dom-if" if="[[showTPMFirmwareUpdateDialog_]]" - restamp> - <os-settings-powerwash-dialog request-tpm-firmware-update - on-close="onPowerwashDialogClose_"> - </os-settings-powerwash-dialog> - </template> - </template> - <script src="os_about_page.js"></script> -</dom-module> + </div> +<if expr="_google_chrome"> + <div class="secondary"> + <a id="aboutProductTos" href="$i18n{aboutTermsURL}" target="_blank" + deep-link-focus-id$="[[Setting.kTermsOfService]]"> + $i18n{aboutProductTos} + </a> + </div> +</if> + </div> + <div class="settings-box padded block" id="regulatoryInfo" + hidden$="[[!shouldShowRegulatoryOrSafetyInfo_(regulatoryInfo_)]]"> +<if expr="_google_chrome"> + <div class="secondary" hidden$="[[!shouldShowSafetyInfo_()]]"> + <a target="_blank" href="$i18n{aboutProductSafetyURL}"> + $i18nRaw{aboutProductSafety} + </a> + </div> +</if> + <img src="[[regulatoryInfo_.url]]" alt="[[regulatoryInfo_.text]]" + hidden$="[[!shouldShowRegulatoryInfo_(regulatoryInfo_)]]"> + </div> +</settings-section> +<template is="dom-if" if="[[showUpdateWarningDialog_]]" restamp> + <settings-update-warning-dialog update-info="[[updateInfo_]]" + on-close="onUpdateWarningDialogClose_"> + </settings-update-warning-dialog> +</template> +<template is="dom-if" if="[[showTPMFirmwareUpdateDialog_]]" + restamp> + <os-settings-powerwash-dialog request-tpm-firmware-update + on-close="onPowerwashDialogClose_"> + </os-settings-powerwash-dialog> +</template>
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js index 14bf0b5..e9f1b73 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js +++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
@@ -7,14 +7,48 @@ * information. */ +import '../../icons.js'; +import '../../prefs/prefs.js'; +import '../../settings_page/settings_animated_pages.js'; +import '../../settings_page/settings_section.js'; +import '../../settings_page/settings_subpage.js'; +import '../../settings_page_css.js'; +import '../../settings_shared_css.js'; +import '../os_icons.m.js'; +import '../os_reset_page/os_powerwash_dialog.js'; +import '../localized_link/localized_link.js'; +import './detailed_build_info.js'; +import './update_warning_dialog.js'; +import '//resources/cr_elements/cr_button/cr_button.m.js'; +import '//resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import '//resources/cr_elements/cr_link_row/cr_link_row.js'; +import '//resources/cr_elements/icons.m.js'; +import '//resources/polymer/v3_0/iron-icon/iron-icon.js'; + +import {assert, assertNotReached} from '//resources/js/assert.m.js'; +import {I18nBehavior} from '//resources/js/i18n_behavior.m.js'; +import {parseHtmlSubset} from '//resources/js/parse_html_subset.m.js'; +import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js'; +import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, ChannelInfo, isTargetChannelMoreStable, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent, VersionInfo} from '../../about_page/about_page_browser_proxy.js'; +import {loadTimeData} from '../../i18n_setup.js'; +import {LifetimeBrowserProxyImpl} from '../../lifetime_browser_proxy.js'; +import {Route, RouteObserverBehavior, Router} from '../../router.js'; +import {DeepLinkingBehavior} from '../deep_linking_behavior.m.js'; +import {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSearch, recordSettingChange, setUserActionRecorderForTesting} from '../metrics_recorder.m.js'; +import {routes} from '../os_route.m.js'; +import {MainPageBehavior} from '../os_settings_page/main_page_behavior.m.js'; + Polymer({ + _template: html`{__html_template__}`, is: 'os-settings-about-page', behaviors: [ DeepLinkingBehavior, WebUIListenerBehavior, - settings.MainPageBehavior, - settings.RouteObserverBehavior, + MainPageBehavior, + RouteObserverBehavior, I18nBehavior, ], @@ -127,10 +161,9 @@ type: Object, value() { const map = new Map(); - if (settings.routes.DETAILED_BUILD_INFO) { + if (routes.DETAILED_BUILD_INFO) { map.set( - settings.routes.DETAILED_BUILD_INFO.path, - '#detailed-build-info-trigger'); + routes.DETAILED_BUILD_INFO.path, '#detailed-build-info-trigger'); } return map; }, @@ -190,12 +223,12 @@ 'handleCrostiniEnabledChanged_(prefs.crostini.enabled.value)', ], - /** @private {?settings.AboutPageBrowserProxy} */ + /** @private {?AboutPageBrowserProxy} */ aboutBrowserProxy_: null, /** @override */ attached() { - this.aboutBrowserProxy_ = settings.AboutPageBrowserProxyImpl.getInstance(); + this.aboutBrowserProxy_ = AboutPageBrowserProxyImpl.getInstance(); this.aboutBrowserProxy_.pageReady(); this.addEventListener('target-channel-changed', e => { @@ -222,22 +255,21 @@ this.hasInternetConnection_ = result; }); - if (settings.Router.getInstance().getQueryParameters().get( - 'checkForUpdate') === 'true') { + if (Router.getInstance().getQueryParameters().get('checkForUpdate') === + 'true') { this.onCheckUpdatesClick_(); } }, /** - * @param {!settings.Route} newRoute - * @param {settings.Route} oldRoute + * @param {!Route} newRoute + * @param {Route} oldRoute */ currentRouteChanged(newRoute, oldRoute) { - settings.MainPageBehavior.currentRouteChanged.call( - this, newRoute, oldRoute); + MainPageBehavior.currentRouteChanged.call(this, newRoute, oldRoute); // Does not apply to this page. - if (newRoute !== settings.routes.ABOUT_ABOUT) { + if (newRoute !== routes.ABOUT_ABOUT) { return; } @@ -253,9 +285,9 @@ }); }, - // Override settings.MainPageBehavior method. + // Override MainPageBehavior method. containsRoute(route) { - return !route || settings.routes.ABOUT.contains(route); + return !route || routes.ABOUT.contains(route); }, /** @private */ @@ -307,13 +339,13 @@ onDiagnosticsClick_() { assert(this.showDiagnosticsApp_); this.aboutBrowserProxy_.openDiagnostics(); - settings.recordSettingChange(chromeos.settings.mojom.Setting.kDiagnostics); + recordSettingChange(chromeos.settings.mojom.Setting.kDiagnostics); }, /** @private */ onRelaunchClick_() { - settings.recordSettingChange(); - settings.LifetimeBrowserProxyImpl.getInstance().relaunch(); + recordSettingChange(); + LifetimeBrowserProxyImpl.getInstance().relaunch(); }, /** @private */ @@ -403,8 +435,8 @@ if (this.currentChannel_ !== this.targetChannel_) { return this.i18nAdvanced('aboutUpgradeUpdatingChannelSwitch', { substitutions: [ - this.i18nAdvanced(settings.browserChannelToI18nId( - this.targetChannel_, this.isLts_)), + this.i18nAdvanced( + browserChannelToI18nId(this.targetChannel_, this.isLts_)), progressPercent ] }); @@ -518,8 +550,7 @@ /** @private */ onDetailedBuildInfoClick_() { - settings.Router.getInstance().navigateTo( - settings.routes.DETAILED_BUILD_INFO); + Router.getInstance().navigateTo(routes.DETAILED_BUILD_INFO); }, /**
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.html b/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.html index 3080573a..bcb0f7d9 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.html +++ b/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.html
@@ -1,28 +1,15 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> - -<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> -<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="../../about_page/about_page_browser_proxy.html"> -<link rel="import" href="../../settings_shared_css.html"> - -<dom-module id="settings-update-warning-dialog"> - <template> - <style include="settings-shared"></style> - <cr-dialog id="dialog" close-text="$i18n{close}"> - <div slot="title">$i18n{aboutUpdateWarningTitle}</div> - <div slot="body"> - <div id="update-warning-message"></div> - </div> - <div slot="button-container"> - <cr-button id="cancel" class="cancel-button" - on-click="onCancelTap_">$i18n{cancel}</cr-button> - <cr-button id="continue" class="action-button" - on-click="onContinueTap_"> - $i18n{continue} - </cr-button> - </div> - </cr-dialog> - </template> - <script src="update_warning_dialog.js"></script> -</dom-module> +<style include="settings-shared"></style> +<cr-dialog id="dialog" close-text="$i18n{close}"> + <div slot="title">$i18n{aboutUpdateWarningTitle}</div> + <div slot="body"> + <div id="update-warning-message"></div> + </div> + <div slot="button-container"> + <cr-button id="cancel" class="cancel-button" + on-click="onCancelTap_">$i18n{cancel}</cr-button> + <cr-button id="continue" class="action-button" + on-click="onContinueTap_"> + $i18n{continue} + </cr-button> + </div> +</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js index e679770..e0e90a3 100644 --- a/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js +++ b/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js
@@ -7,7 +7,17 @@ * user about update over mobile data. By clicking 'Continue', the user * agrees to download update using mobile data. */ +import '//resources/cr_elements/cr_button/cr_button.m.js'; +import '//resources/cr_elements/cr_dialog/cr_dialog.m.js'; +import '../../settings_shared_css.js'; + +import {I18nBehavior} from '//resources/js/i18n_behavior.m.js'; +import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, ChannelInfo, isTargetChannelMoreStable, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent, VersionInfo} from '../../about_page/about_page_browser_proxy.js'; + Polymer({ + _template: html`{__html_template__}`, is: 'settings-update-warning-dialog', behaviors: [I18nBehavior], @@ -20,12 +30,12 @@ }, }, - /** @private {?settings.AboutPageBrowserProxy} */ + /** @private {?AboutPageBrowserProxy} */ browserProxy_: null, /** @override */ ready() { - this.browserProxy_ = settings.AboutPageBrowserProxyImpl.getInstance(); + this.browserProxy_ = AboutPageBrowserProxyImpl.getInstance(); }, /** @override */
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn index 5956dfe01..a34f989 100644 --- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn +++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
@@ -13,6 +13,8 @@ ":os_bluetooth_devices_subpage", ":os_bluetooth_page", ":os_bluetooth_summary", + ":os_paired_bluetooth_list", + ":os_paired_bluetooth_list_item", ] } @@ -29,7 +31,9 @@ js_library("os_bluetooth_devices_subpage") { deps = [ + ":os_paired_bluetooth_list", "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", + "//ui/webui/resources/js:i18n_behavior.m", ] } @@ -43,8 +47,24 @@ ] } +js_library("os_paired_bluetooth_list") { + deps = [ + ":os_paired_bluetooth_list_item", + "//third_party/polymer/v3_0/components-chromium/iron-list:iron-list", + "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", + ] +} + +js_library("os_paired_bluetooth_list_item") { + deps = [ + "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", + ] +} + html_to_js("web_components") { js_files = [ + "os_paired_bluetooth_list_item.js", + "os_paired_bluetooth_list.js", "os_bluetooth_devices_subpage.js", "os_bluetooth_page.js", "os_bluetooth_summary.js",
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.html b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.html index a769441..767e2b0 100644 --- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.html +++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.html
@@ -1,5 +1,29 @@ <style include="settings-shared"> + #container { + padding-inline-end: calc(var(--cr-section-padding) - + var(--cr-icon-ripple-padding)); + padding-inline-start: var(--cr-section-padding); + } + + .device-list { + margin-inline-start: 32px; + } </style> <div id="container"> - <!-- TODO(crbug.com/1010321): Add bluetooth devices list --> + <!-- TODO(crbug.com/1010321): Add toggle and pair new device button. --> + <!-- TODO(crbug.com/1010321): Populate with real data. --> + <div class="settings-box-text"> + $i18n{bluetoothDeviceListCurrentlyConnected} + </div> + <div class="device-list"> + <os-settings-paired-bluetooth-list> + </os-settings-paired-bluetooth-list> + </div> + <div class="settings-box-text"> + $i18n{bluetoothDeviceListPreviouslyConnected} + </div> + <div class="device-list"> + <os-settings-paired-bluetooth-list> + </os-settings-paired-bluetooth-list> + </div> </div> \ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js index f6db680..7270b3a 100644 --- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js +++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js
@@ -8,11 +8,22 @@ */ import '../../settings_shared_css.js'; +import './os_paired_bluetooth_list.js'; -import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {I18nBehavior, I18nBehaviorInterface} from '//resources/js/i18n_behavior.m.js'; +import {html, mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +/** + * @constructor + * @extends {PolymerElement} + * @implements {I18nBehaviorInterface} + */ +const SettingsBluetoothDevicesSubpageElementBase = + mixinBehaviors([I18nBehavior], PolymerElement); /** @polymer */ -class SettingsBluetoothDevicesSubpageElement extends PolymerElement { +class SettingsBluetoothDevicesSubpageElement extends + SettingsBluetoothDevicesSubpageElementBase { static get is() { return 'os-settings-bluetooth-devices-subpage'; }
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html new file mode 100644 index 0000000..de4e45a9 --- /dev/null +++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html
@@ -0,0 +1,17 @@ +<style include="settings-shared"> + iron-list > *:not(:first-of-type) { + border-top: var(--cr-separator-line); + } +</style> +<!-- TODO(crbug.com/1010321): Populate with real data. --> +<div id="container" class="layout vertical flex" scrollable + no-bottom-scroll-border> + <iron-list items="[[devices_]]" scroll-target="container" preserve-focus> + <template> + <!-- TODO(crbug.com/1010321): Add focus behavior, + fix not all elements rendering in large lists. --> + <os-settings-paired-bluetooth-list-item> + </os-settings-paired-bluetooth-list-item> + </template> + </iron-list> +</div> \ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.js new file mode 100644 index 0000000..086cb74d --- /dev/null +++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.js
@@ -0,0 +1,43 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview + * UI element for displaying paired Bluetooth devices. + */ + +import '../../settings_shared_css.js'; +import './os_paired_bluetooth_list_item.js'; + +import '//resources/polymer/v3_0/iron-list/iron-list.js'; +import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +/** @polymer */ +class SettingsPairedBluetoothListElement extends PolymerElement { + static get is() { + return 'os-settings-paired-bluetooth-list'; + } + + static get template() { + return html`{__html_template__}`; + } + + static get properties() { + return { + /** + * TODO(crbug.com/1010321): Use actual Device objects. + * @private {Array<Object>} + */ + devices_: { + type: Array, + value() { + return [{}, {}, {}]; + } + } + }; + } +} + +customElements.define( + SettingsPairedBluetoothListElement.is, SettingsPairedBluetoothListElement); \ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.html b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.html new file mode 100644 index 0000000..22d9b88 --- /dev/null +++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.html
@@ -0,0 +1,34 @@ +<style include="settings-shared"> + .battery { + color: var(--google-green-500); + } +</style> +<div focus-row-container> + <!-- TODO(crbug.com/1010321): Populate with dynamic data. --> + <!-- TODO(crbug.com/1010321): Add a11y label. --> + <div class="list-item" + focus-row-control + focus-type="rowWrapper" + role="button" + selectable> + <!-- TODO(crbug.com/1010321): Add updated device type icon. --> + <iron-icon icon="os-settings:headset"> + </iron-icon> + <div class="middle" aria-hidden="true"> + <div> + Device Name + </div> + <!-- TODO(crbug.com/1010321): Add battery icon. --> + <div class="battery secondary"> + 96% + </div> + </div> + <div> + <!-- TODO(crbug.com/1010321): Add click listener. --> + <cr-icon-button class="subpage-arrow" + focus-row-control + focus-type="subpageButton"> + </cr-icon-button> + </div> + </div> +</div> \ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js new file mode 100644 index 0000000..82f5df18 --- /dev/null +++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js
@@ -0,0 +1,30 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview + * Item in <os-paired_bluetooth-list> that displays information for a paired + * Bluetooth device. + */ + +import '../../settings_shared_css.js'; +import '//resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import '../os_icons.m.js'; + +import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +/** @polymer */ +class SettingsPairedBluetoothListItemElement extends PolymerElement { + static get is() { + return 'os-settings-paired-bluetooth-list-item'; + } + + static get template() { + return html`{__html_template__}`; + } +} + +customElements.define( + SettingsPairedBluetoothListItemElement.is, + SettingsPairedBluetoothListItemElement); \ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni index 3c325cc..26187c9 100644 --- a/chrome/browser/resources/settings/chromeos/os_settings.gni +++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -332,11 +332,19 @@ "chrome/browser/resources/settings/chromeos/internet_page/tether_connection_dialog.html", "chrome/browser/resources/settings/chromeos/localized_link/localized_link.html", "chrome/browser/resources/settings/chromeos/on_startup_page/on_startup_page.html", + "chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.html", + "chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.html", + "chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.html", + "chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.html", + "chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html", + "chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.html", "chrome/browser/resources/settings/chromeos/os_a11y_page/change_dictation_locale_dialog.html", "chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html", "chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_page.html", "chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.html", "chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_summary.html", + "chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html", + "chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.html", "chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.html", "chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.html", "chrome/browser/resources/settings/chromeos/os_languages_page/add_spellcheck_languages_dialog.html",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js index 9b874cf..3eb427fb 100644 --- a/chrome/browser/resources/settings/chromeos/os_settings.js +++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -43,10 +43,10 @@ import './personalization_page/dark_mode_subpage.m.js'; import './personalization_page/personalization_page.m.js'; import './os_a11y_page/change_dictation_locale_dialog.js'; -import './os_about_page/channel_switcher_dialog.m.js'; -import './os_about_page/detailed_build_info.m.js'; -import './os_about_page/os_about_page.m.js'; -import './os_about_page/update_warning_dialog.m.js'; +import './os_about_page/channel_switcher_dialog.js'; +import './os_about_page/detailed_build_info.js'; +import './os_about_page/os_about_page.js'; +import './os_about_page/update_warning_dialog.js'; import './os_apps_page/android_apps_subpage.m.js'; import './os_apps_page/app_notifications_page/app_notifications_subpage.js'; import './os_apps_page/app_management_page/app_item.m.js'; @@ -72,6 +72,8 @@ import './os_bluetooth_page/os_bluetooth_devices_subpage.js'; import './os_bluetooth_page/os_bluetooth_page.js'; import './os_bluetooth_page/os_bluetooth_summary.js'; +import './os_bluetooth_page/os_paired_bluetooth_list.js'; +import './os_bluetooth_page/os_paired_bluetooth_list_item.js'; import './os_icons.m.js'; import './os_people_page/account_manager.m.js'; import './os_people_page/os_people_page.m.js'; @@ -120,7 +122,7 @@ export {OsA11yPageBrowserProxy, OsA11yPageBrowserProxyImpl} from './os_a11y_page/os_a11y_page_browser_proxy.m.js'; export {SwitchAccessSubpageBrowserProxy, SwitchAccessSubpageBrowserProxyImpl} from './os_a11y_page/switch_access_subpage_browser_proxy.m.js'; export {TtsSubpageBrowserProxy, TtsSubpageBrowserProxyImpl} from './os_a11y_page/tts_subpage_browser_proxy.m.js'; -export {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl} from './os_about_page/device_name_browser_proxy.m.js'; +export {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl} from './os_about_page/device_name_browser_proxy.js'; export {AndroidAppsBrowserProxyImpl} from './os_apps_page/android_apps_browser_proxy.m.js'; export {addApp, changeApp, removeApp, updateSelectedAppId} from './os_apps_page/app_management_page/actions.m.js'; export {BrowserProxy} from './os_apps_page/app_management_page/browser_proxy.m.js';
diff --git a/chrome/browser/resources/settings/extension_control_browser_proxy.ts b/chrome/browser/resources/settings/extension_control_browser_proxy.ts index d317784..3e330d69 100644 --- a/chrome/browser/resources/settings/extension_control_browser_proxy.ts +++ b/chrome/browser/resources/settings/extension_control_browser_proxy.ts
@@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {addSingletonGetter} from 'chrome://resources/js/cr.m.js'; - export interface ExtensionControlBrowserProxy { // TODO(dbeam): should be be returning !Promise<boolean> to indicate whether // it succeeded? @@ -21,6 +19,14 @@ manageExtension(extensionId: string) { window.open('chrome://extensions?id=' + extensionId); } + + static getInstance(): ExtensionControlBrowserProxy { + return instance || (instance = new ExtensionControlBrowserProxyImpl()); + } + + static setInstance(obj: ExtensionControlBrowserProxy) { + instance = obj; + } } -addSingletonGetter(ExtensionControlBrowserProxyImpl); +let instance: ExtensionControlBrowserProxy|null = null;
diff --git a/chrome/browser/resources/settings/search_engines_page/BUILD.gn b/chrome/browser/resources/settings/search_engines_page/BUILD.gn index 93123272..e0761b74 100644 --- a/chrome/browser/resources/settings/search_engines_page/BUILD.gn +++ b/chrome/browser/resources/settings/search_engines_page/BUILD.gn
@@ -2,84 +2,15 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//third_party/closure_compiler/compile_js.gni") import("//tools/polymer/html_to_js.gni") -import("../settings.gni") - -js_type_check("closure_compile") { - is_polymer3 = true - closure_flags = settings_closure_flags - deps = [ - ":omnibox_extension_entry", - ":search_engine_dialog", - ":search_engine_entry", - ":search_engines_browser_proxy", - ":search_engines_list", - ":search_engines_page", - ] -} - -js_library("omnibox_extension_entry") { - deps = [ - ":search_engines_browser_proxy", - "..:extension_control_browser_proxy", - "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", - "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu", - "//ui/webui/resources/js:assert.m", - "//ui/webui/resources/js/cr/ui:focus_row_behavior.m", - ] -} - -js_library("search_engine_dialog") { - deps = [ - ":search_engines_browser_proxy", - "..:i18n_setup", - "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", - "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m", - "//ui/webui/resources/js:web_ui_listener_behavior.m", - ] -} - -js_library("search_engine_entry") { - deps = [ - ":search_engines_browser_proxy", - "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", - "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu", - "//ui/webui/resources/js:assert.m", - "//ui/webui/resources/js/cr/ui:focus_row_behavior.m", - ] -} - -js_library("search_engines_browser_proxy") { - deps = [ "//ui/webui/resources/js:cr.m" ] -} - -js_library("search_engines_list") { - deps = [ - ":search_engines_browser_proxy", - "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", - ] -} - -js_library("search_engines_page") { - deps = [ - ":search_engines_browser_proxy", - "..:global_scroll_target_mixin", - "..:settings_routes", - "//third_party/polymer/v3_0/components-chromium/iron-list", - "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", - "//ui/webui/resources/js:assert.m", - "//ui/webui/resources/js:web_ui_listener_behavior.m", - ] -} html_to_js("web_components") { js_files = [ - "omnibox_extension_entry.js", - "search_engine_dialog.js", - "search_engine_entry_css.js", - "search_engine_entry.js", - "search_engines_list.js", - "search_engines_page.js", + "omnibox_extension_entry.ts", + "search_engine_dialog.ts", + "search_engine_entry_css.ts", + "search_engine_entry.ts", + "search_engines_list.ts", + "search_engines_page.ts", ] }
diff --git a/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.js b/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.js deleted file mode 100644 index 440d44f..0000000 --- a/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.js +++ /dev/null
@@ -1,88 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/** - * @fileoverview 'settings-omnibox-extension-entry' is a component for showing - * an omnibox extension with its name and keyword. - */ -import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; -import 'chrome://resources/cr_elements/icons.m.js'; -import './search_engine_entry_css.js'; -import '../settings_shared_css.js'; -import '../site_favicon.js'; - -import {AnchorAlignment, CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js'; -import {assert} from 'chrome://resources/js/assert.m.js'; -import {FocusRowBehavior, FocusRowBehaviorInterface} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js'; -import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - -import {ExtensionControlBrowserProxyImpl} from '../extension_control_browser_proxy.js'; - -import {SearchEngine} from './search_engines_browser_proxy.js'; - - -/** - * @constructor - * @extends {PolymerElement} - * @implements {FocusRowBehaviorInterface} - */ -const SettingsOmniboxExtensionEntryElementBase = - mixinBehaviors([FocusRowBehavior], PolymerElement); - -/** @polymer */ -class SettingsOmniboxExtensionEntryElement extends - SettingsOmniboxExtensionEntryElementBase { - static get is() { - return 'settings-omnibox-extension-entry'; - } - - static get template() { - return html`{__html_template__}`; - } - - static get properties() { - return { - /** @type {!SearchEngine} */ - engine: Object, - }; - } - - /** @override */ - constructor() { - super(); - - /** @private {!ExtensionControlBrowserProxyImpl} */ - this.browserProxy_ = ExtensionControlBrowserProxyImpl.getInstance(); - } - - /** @private */ - onManageTap_() { - this.closePopupMenu_(); - this.browserProxy_.manageExtension(this.engine.extension.id); - } - - /** @private */ - onDisableTap_() { - this.closePopupMenu_(); - this.browserProxy_.disableExtension(this.engine.extension.id); - } - - /** @private */ - closePopupMenu_() { - this.shadowRoot.querySelector('cr-action-menu').close(); - } - - /** @private */ - onDotsTap_() { - /** @type {!CrActionMenuElement} */ ( - this.shadowRoot.querySelector('cr-action-menu')) - .showAt(assert(this.shadowRoot.querySelector('cr-icon-button')), { - anchorAlignmentY: AnchorAlignment.AFTER_END, - }); - } -} - -customElements.define( - SettingsOmniboxExtensionEntryElement.is, - SettingsOmniboxExtensionEntryElement);
diff --git a/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.ts b/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.ts new file mode 100644 index 0000000..2e15701f --- /dev/null +++ b/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.ts
@@ -0,0 +1,72 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview 'settings-omnibox-extension-entry' is a component for showing + * an omnibox extension with its name and keyword. + */ +import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; +import 'chrome://resources/cr_elements/icons.m.js'; +import './search_engine_entry_css.js'; +import '../settings_shared_css.js'; +import '../site_favicon.js'; + +import {AnchorAlignment} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js'; +import {assert} from 'chrome://resources/js/assert.m.js'; +import {FocusRowBehavior} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js'; +import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {ExtensionControlBrowserProxy, ExtensionControlBrowserProxyImpl} from '../extension_control_browser_proxy.js'; + +import {SearchEngine} from './search_engines_browser_proxy.js'; + +const SettingsOmniboxExtensionEntryElementBase = + mixinBehaviors([FocusRowBehavior], PolymerElement) as + {new (): PolymerElement & FocusRowBehavior}; + +class SettingsOmniboxExtensionEntryElement extends + SettingsOmniboxExtensionEntryElementBase { + static get is() { + return 'settings-omnibox-extension-entry'; + } + + static get template() { + return html`{__html_template__}`; + } + + static get properties() { + return { + engine: Object, + }; + } + + engine: SearchEngine; + private browserProxy_: ExtensionControlBrowserProxy = + ExtensionControlBrowserProxyImpl.getInstance(); + + private onManageTap_() { + this.closePopupMenu_(); + this.browserProxy_.manageExtension(this.engine.extension!.id); + } + + private onDisableTap_() { + this.closePopupMenu_(); + this.browserProxy_.disableExtension(this.engine.extension!.id); + } + + private closePopupMenu_() { + this.shadowRoot!.querySelector('cr-action-menu')!.close(); + } + + private onDotsTap_() { + this.shadowRoot!.querySelector('cr-action-menu')!.showAt( + assert(this.shadowRoot!.querySelector('cr-icon-button')!), { + anchorAlignmentY: AnchorAlignment.AFTER_END, + }); + } +} + +customElements.define( + SettingsOmniboxExtensionEntryElement.is, + SettingsOmniboxExtensionEntryElement);
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.js b/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.ts similarity index 74% rename from chrome/browser/resources/settings/search_engines_page/search_engine_dialog.js rename to chrome/browser/resources/settings/search_engines_page/search_engine_dialog.ts index 6a7cd58..1295670 100644 --- a/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.js +++ b/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.ts
@@ -10,23 +10,30 @@ import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js'; import 'chrome://resources/cr_elements/cr_input/cr_input.m.js'; -import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/js/web_ui_listener_behavior.m.js'; +import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js'; +import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js'; +import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js'; import {html, microTask, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {loadTimeData} from '../i18n_setup.js'; import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl, SearchEnginesInfo} from './search_engines_browser_proxy.js'; +interface SettingsSearchEngineDialogElement { + $: { + actionButton: CrButtonElement, + dialog: CrDialogElement, + keyword: CrInputElement, + queryUrl: CrInputElement, + searchEngine: CrInputElement, + }; +} -/** - * @constructor - * @extends {PolymerElement} - * @implements {WebUIListenerBehaviorInterface} - */ const SettingsSearchEngineDialogElementBase = - mixinBehaviors([WebUIListenerBehavior], PolymerElement); + mixinBehaviors([WebUIListenerBehavior], PolymerElement) as + {new (): PolymerElement & WebUIListenerBehavior}; -/** @polymer */ class SettingsSearchEngineDialogElement extends SettingsSearchEngineDialogElementBase { static get is() { @@ -42,45 +49,38 @@ /** * The search engine to be edited. If not populated a new search engine * should be added. - * @type {?SearchEngine} */ model: Object, - /** @private {string} */ searchEngine_: String, - - /** @private {string} */ keyword_: String, - - /** @private {string} */ queryUrl_: String, - - /** @private {string} */ dialogTitle_: String, - - /** @private {string} */ actionButtonText_: String, - }; } - /** @override */ + model: SearchEngine|null; + private searchEngine_: string; + private keyword_: string; + private queryUrl_: string; + private dialogTitle_: string; + private actionButtonText_: string; + private browserProxy_: SearchEnginesBrowserProxy = + SearchEnginesBrowserProxyImpl.getInstance(); + DEFAULT_MODEL_INDEX: number; + constructor() { super(); - /** @private {SearchEnginesBrowserProxy} */ - this.browserProxy_ = SearchEnginesBrowserProxyImpl.getInstance(); - /** * The |modelIndex| to use when a new search engine is added. Must match * with kNewSearchEngineIndex constant specified at * chrome/browser/ui/webui/settings/search_engines_handler.cc - * @type {number} */ this.DEFAULT_MODEL_INDEX = -1; } - /** @override */ ready() { super.ready(); @@ -107,7 +107,6 @@ 'search-engines-changed', this.enginesChanged_.bind(this)); } - /** @override */ connectedCallback() { super.connectedCallback(); @@ -117,16 +116,12 @@ this.$.dialog.showModal(); } - /** - * @param {!SearchEnginesInfo} searchEnginesInfo - * @private - */ - enginesChanged_(searchEnginesInfo) { + private enginesChanged_(searchEnginesInfo: SearchEnginesInfo) { if (this.model) { const engineWasRemoved = ['defaults', 'actives', 'others', 'extensions'].every( engineType => searchEnginesInfo[engineType].every( - e => e.id !== this.model.id)); + e => e.id !== this.model!.id)); if (engineWasRemoved) { this.cancel_(); return; @@ -137,23 +132,17 @@ element => this.validateElement_(element)); } - /** @private */ - cancel_() { - /** @type {!CrDialogElement} */ (this.$.dialog).cancel(); + private cancel_() { + this.$.dialog.cancel(); } - /** @private */ - onActionButtonTap_() { + private onActionButtonTap_() { this.browserProxy_.searchEngineEditCompleted( this.searchEngine_, this.keyword_, this.queryUrl_); this.$.dialog.close(); } - /** - * @param {!Element} inputElement - * @private - */ - validateElement_(inputElement) { + private validateElement_(inputElement: CrInputElement) { // If element is empty, disable the action button, but don't show the red // invalid message. if (inputElement.value === '') { @@ -170,17 +159,12 @@ }); } - /** - * @param {!Event} event - * @private - */ - validate_(event) { - const inputElement = /** @type {!Element} */ (event.target); + private validate_(event: Event) { + const inputElement = event.target as CrInputElement; this.validateElement_(inputElement); } - /** @private */ - updateActionButtonState_() { + private updateActionButtonState_() { const allValid = [ this.$.searchEngine, this.$.keyword, this.$.queryUrl ].every(function(inputElement) {
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js b/chrome/browser/resources/settings/search_engines_page/search_engine_entry.ts similarity index 61% rename from chrome/browser/resources/settings/search_engines_page/search_engine_entry.js rename to chrome/browser/resources/settings/search_engines_page/search_engine_entry.ts index 613d3b4..ab954b7c 100644 --- a/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js +++ b/chrome/browser/resources/settings/search_engines_page/search_engine_entry.ts
@@ -13,23 +13,17 @@ import '../settings_shared_css.js'; import '../site_favicon.js'; -import {AnchorAlignment, CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js'; +import {AnchorAlignment} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js'; import {assert} from 'chrome://resources/js/assert.m.js'; -import {FocusRowBehavior, FocusRowBehaviorInterface} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js'; +import {FocusRowBehavior} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js'; import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl} from './search_engines_browser_proxy.js'; - -/** - * @constructor - * @extends {PolymerElement} - * @implements {FocusRowBehaviorInterface} - */ const SettingsSearchEngineEntryElementBase = - mixinBehaviors([FocusRowBehavior], PolymerElement); + mixinBehaviors([FocusRowBehavior], PolymerElement) as + {new (): PolymerElement & FocusRowBehavior}; -/** @polymer */ class SettingsSearchEngineEntryElement extends SettingsSearchEngineEntryElementBase { static get is() { @@ -42,10 +36,8 @@ static get properties() { return { - /** @type {!SearchEngine} */ engine: Object, - /** @type {boolean} */ isDefault: { reflectToAttribute: true, type: Boolean, @@ -55,47 +47,32 @@ }; } - /** @override */ - constructor() { - super(); + engine: SearchEngine; + isDefault: boolean; + private browserProxy_: SearchEnginesBrowserProxy = + SearchEnginesBrowserProxyImpl.getInstance(); - /** @private {!SearchEnginesBrowserProxy} */ - this.browserProxy_ = SearchEnginesBrowserProxyImpl.getInstance(); + private closePopupMenu_() { + this.shadowRoot!.querySelector('cr-action-menu')!.close(); } - /** @private */ - closePopupMenu_() { - this.shadowRoot.querySelector('cr-action-menu').close(); - } - - /** - * @return {boolean} - * @private - */ - computeIsDefault_() { + private computeIsDefault_(): boolean { return this.engine.default; } - /** @private */ - onDeleteTap_() { + private onDeleteTap_() { this.browserProxy_.removeSearchEngine(this.engine.modelIndex); this.closePopupMenu_(); } - /** @private */ - onDotsTap_() { - /** @type {!CrActionMenuElement} */ ( - this.shadowRoot.querySelector('cr-action-menu')) - .showAt(assert(this.shadowRoot.querySelector('cr-icon-button')), { + private onDotsTap_() { + this.shadowRoot!.querySelector('cr-action-menu')!.showAt( + assert(this.shadowRoot!.querySelector('cr-icon-button')!), { anchorAlignmentY: AnchorAlignment.AFTER_END, }); } - /** - * @param {!Event} e - * @private - */ - onEditTap_(e) { + private onEditTap_(e: Event) { e.preventDefault(); this.closePopupMenu_(); this.dispatchEvent(new CustomEvent('edit-search-engine', { @@ -103,13 +80,13 @@ composed: true, detail: { engine: this.engine, - anchorElement: assert(this.shadowRoot.querySelector('cr-icon-button')), + anchorElement: + assert(this.shadowRoot!.querySelector('cr-icon-button')!), }, })); } - /** @private */ - onMakeDefaultTap_() { + private onMakeDefaultTap_() { this.closePopupMenu_(); this.browserProxy_.setDefaultSearchEngine(this.engine.modelIndex); }
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.js b/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.ts similarity index 96% rename from chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.js rename to chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.ts index ffec81b..1b80523e 100644 --- a/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.js +++ b/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.ts
@@ -7,3 +7,4 @@ <dom-module id="search-engine-entry">{__html_template__}</dom-module> `; document.body.appendChild(template.content.cloneNode(true)); +export {};
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.js b/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.js deleted file mode 100644 index 6c99744..0000000 --- a/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.js +++ /dev/null
@@ -1,123 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// clang-format off -import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js'; -// clang-format on - -/** - * @fileoverview A helper object used from the "Manage search engines" section - * to interact with the browser. - */ - -/** - * @typedef {{canBeDefault: boolean, - * canBeEdited: boolean, - * canBeRemoved: boolean, - * default: boolean, - * displayName: string, - * extension: ({id: string, - * name: string, - * canBeDisabled: boolean, - * icon: string}|undefined), - * iconURL: (string|undefined), - * id: number, - * isOmniboxExtension: boolean, - * keyword: string, - * modelIndex: number, - * name: string, - * url: string, - * urlLocked: boolean}} - * @see chrome/browser/ui/webui/settings/search_engine_manager_handler.cc - */ -export let SearchEngine; - -/** - * @typedef {{ - * defaults: !Array<!SearchEngine>, - * actives: !Array<!SearchEngine>, - * others: !Array<!SearchEngine>, - * extensions: !Array<!SearchEngine> - * }} - */ -export let SearchEnginesInfo; - -/** @interface */ -export class SearchEnginesBrowserProxy { - /** @param {number} modelIndex */ - setDefaultSearchEngine(modelIndex) {} - - /** @param {number} modelIndex */ - removeSearchEngine(modelIndex) {} - - /** @param {number} modelIndex */ - searchEngineEditStarted(modelIndex) {} - - searchEngineEditCancelled() {} - - /** - * @param {string} searchEngine - * @param {string} keyword - * @param {string} queryUrl - */ - searchEngineEditCompleted(searchEngine, keyword, queryUrl) {} - - /** @return {!Promise<!SearchEnginesInfo>} */ - getSearchEnginesList() {} - - /** - * @param {string} fieldName - * @param {string} fieldValue - * @return {!Promise<boolean>} - */ - validateSearchEngineInput(fieldName, fieldValue) {} -} - -/** - * @implements {SearchEnginesBrowserProxy} - */ -export class SearchEnginesBrowserProxyImpl { - /** @override */ - setDefaultSearchEngine(modelIndex) { - chrome.send('setDefaultSearchEngine', [modelIndex]); - } - - /** @override */ - removeSearchEngine(modelIndex) { - chrome.send('removeSearchEngine', [modelIndex]); - } - - /** @override */ - searchEngineEditStarted(modelIndex) { - chrome.send('searchEngineEditStarted', [modelIndex]); - } - - /** @override */ - searchEngineEditCancelled() { - chrome.send('searchEngineEditCancelled'); - } - - /** @override */ - searchEngineEditCompleted(searchEngine, keyword, queryUrl) { - chrome.send('searchEngineEditCompleted', [ - searchEngine, - keyword, - queryUrl, - ]); - } - - /** @override */ - getSearchEnginesList() { - return sendWithPromise('getSearchEnginesList'); - } - - /** @override */ - validateSearchEngineInput(fieldName, fieldValue) { - return sendWithPromise('validateSearchEngineInput', fieldName, fieldValue); - } -} - - // The singleton instance_ is replaced with a test version of this wrapper - // during testing. -addSingletonGetter(SearchEnginesBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.ts b/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.ts new file mode 100644 index 0000000..fec9a57 --- /dev/null +++ b/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.ts
@@ -0,0 +1,104 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// clang-format off +import {sendWithPromise} from 'chrome://resources/js/cr.m.js'; +// clang-format on + +/** + * @fileoverview A helper object used from the "Manage search engines" section + * to interact with the browser. + */ + +/** + * @see chrome/browser/ui/webui/settings/search_engine_manager_handler.cc + */ +export type SearchEngine = { + canBeDefault: boolean, + canBeEdited: boolean, + canBeRemoved: boolean, + default: boolean, + displayName: string, + extension?: {id: string, name: string, canBeDisabled: boolean, icon: string}, + iconURL?: string, + id: number, + isOmniboxExtension: boolean, + keyword: string, + modelIndex: number, + name: string, + url: string, + urlLocked: boolean, +}; + +export type SearchEnginesInfo = { + defaults: Array<SearchEngine>, + actives: Array<SearchEngine>, + others: Array<SearchEngine>, + extensions: Array<SearchEngine>, + [key: string]: Array<SearchEngine>, +}; + +export interface SearchEnginesBrowserProxy { + setDefaultSearchEngine(modelIndex: number): void; + + removeSearchEngine(modelIndex: number): void; + + searchEngineEditStarted(modelIndex: number): void; + + searchEngineEditCancelled(): void; + + searchEngineEditCompleted( + searchEngine: string, keyword: string, queryUrl: string): void; + + getSearchEnginesList(): Promise<SearchEnginesInfo>; + + validateSearchEngineInput(fieldName: string, fieldValue: string): + Promise<boolean> +} + +export class SearchEnginesBrowserProxyImpl implements + SearchEnginesBrowserProxy { + setDefaultSearchEngine(modelIndex: number) { + chrome.send('setDefaultSearchEngine', [modelIndex]); + } + + removeSearchEngine(modelIndex: number) { + chrome.send('removeSearchEngine', [modelIndex]); + } + + searchEngineEditStarted(modelIndex: number) { + chrome.send('searchEngineEditStarted', [modelIndex]); + } + + searchEngineEditCancelled() { + chrome.send('searchEngineEditCancelled'); + } + + searchEngineEditCompleted( + searchEngine: string, keyword: string, queryUrl: string) { + chrome.send('searchEngineEditCompleted', [ + searchEngine, + keyword, + queryUrl, + ]); + } + + getSearchEnginesList() { + return sendWithPromise('getSearchEnginesList'); + } + + validateSearchEngineInput(fieldName: string, fieldValue: string) { + return sendWithPromise('validateSearchEngineInput', fieldName, fieldValue); + } + + static getInstance(): SearchEnginesBrowserProxy { + return instance || (instance = new SearchEnginesBrowserProxyImpl()); + } + + static setInstance(obj: SearchEnginesBrowserProxy) { + instance = obj; + } +} + +let instance: SearchEnginesBrowserProxy|null = null;
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engines_list.js b/chrome/browser/resources/settings/search_engines_page/search_engines_list.ts similarity index 83% rename from chrome/browser/resources/settings/search_engines_page/search_engines_list.js rename to chrome/browser/resources/settings/search_engines_page/search_engines_list.ts index 127f5cb..d7100d31 100644 --- a/chrome/browser/resources/settings/search_engines_page/search_engines_list.js +++ b/chrome/browser/resources/settings/search_engines_page/search_engines_list.ts
@@ -16,9 +16,7 @@ import {SearchEngine} from './search_engines_browser_proxy.js'; - -/** @polymer */ -class SettingsSearchEnginesListElement extends PolymerElement { +export class SettingsSearchEnginesListElement extends PolymerElement { static get is() { return 'settings-search-engines-list'; } @@ -29,22 +27,18 @@ static get properties() { return { - /** @type {!Array<!SearchEngine>} */ engines: Array, /** * The scroll target that this list should use. - * @type {?HTMLElement} */ scrollTarget: Object, /** Used to fix scrolling glitch when list is not top most element. */ scrollOffset: Number, - /** @private {Object}*/ lastFocused_: Object, - /** @private */ listBlurred_: Boolean, fixedHeight: { @@ -52,10 +46,16 @@ value: false, reflectToAttribute: true, }, - }; } + + engines: Array<SearchEngine>; + scrollTarget: HTMLElement|null; + scrollOffset: number; + fixedHeight: boolean; + private lastFocused_: HTMLElement; + private listBlurred_: boolean; } customElements.define( - SettingsSearchEnginesListElement.is, SettingsSearchEnginesListElement); \ No newline at end of file + SettingsSearchEnginesListElement.is, SettingsSearchEnginesListElement);
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engines_page.js b/chrome/browser/resources/settings/search_engines_page/search_engines_page.ts similarity index 68% rename from chrome/browser/resources/settings/search_engines_page/search_engines_page.js rename to chrome/browser/resources/settings/search_engines_page/search_engines_page.ts index 9fd6e7d0..a08d6397 100644 --- a/chrome/browser/resources/settings/search_engines_page/search_engines_page.js +++ b/chrome/browser/resources/settings/search_engines_page/search_engines_page.ts
@@ -21,7 +21,8 @@ import {assert} from 'chrome://resources/js/assert.m.js'; import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js'; -import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/js/web_ui_listener_behavior.m.js'; +import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js'; +import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js'; import {afterNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {GlobalScrollTargetMixin} from '../global_scroll_target_mixin.js'; @@ -29,25 +30,26 @@ import {routes} from '../route.js'; import {SearchEngine, SearchEnginesBrowserProxyImpl, SearchEnginesInfo} from './search_engines_browser_proxy.js'; +import {SettingsSearchEnginesListElement} from './search_engines_list.js'; -/** - * @typedef {!CustomEvent<!{ - * engine: !SearchEngine, - * anchorElement: !HTMLElement - * }>} - */ -let SearchEngineEditEvent; +type SearchEngineEditEvent = CustomEvent<{ + engine: SearchEngine, + anchorElement: HTMLElement, +}>; +interface SettingsSearchEnginesPageElement { + $: { + addSearchEngine: HTMLElement, + extensions: IronListElement, + otherEngines: SettingsSearchEnginesListElement, + }; +} -/** - * @constructor - * @extends {PolymerElement} - * @implements {WebUIListenerBehaviorInterface} - */ -const SettingsSearchEnginesPageElementBase = mixinBehaviors( - [WebUIListenerBehavior], GlobalScrollTargetMixin(PolymerElement)); +const SettingsSearchEnginesPageElementBase = + mixinBehaviors( + [WebUIListenerBehavior], GlobalScrollTargetMixin(PolymerElement)) as + {new (): PolymerElement & WebUIListenerBehavior}; -/** @polymer */ class SettingsSearchEnginesPageElement extends SettingsSearchEnginesPageElementBase { static get is() { @@ -68,28 +70,19 @@ notify: true, }, - /** @type {!Array<!SearchEngine>} */ defaultEngines: Array, - - /** @type {!Array<!SearchEngine>} */ activeEngines: Array, - - /** @type {!Array<!SearchEngine>} */ otherEngines: Array, - - /** @type {!Array<!SearchEngine>} */ extensions: Array, /** * Needed by GlobalScrollTargetMixin. - * @override */ subpageRoute: { type: Object, value: routes.SEARCH_ENGINES, }, - /** @private {boolean} */ showExtensionsList_: { type: Boolean, computed: 'computeShowExtensionsList_(extensions)', @@ -101,66 +94,53 @@ value: '', }, - /** @private {!Array<!SearchEngine>} */ matchingDefaultEngines_: { type: Array, computed: 'computeMatchingEngines_(defaultEngines, filter)', }, - /** @private {!Array<!SearchEngine>} */ matchingActiveEngines_: { type: Array, computed: 'computeMatchingEngines_(activeEngines, filter)', }, - /** @private {!Array<!SearchEngine>} */ matchingOtherEngines_: { type: Array, computed: 'computeMatchingEngines_(otherEngines, filter)', }, - /** @private {!Array<!SearchEngine>} */ matchingExtensions_: { type: Array, computed: 'computeMatchingEngines_(extensions, filter)', }, - /** @private {HTMLElement} */ omniboxExtensionlastFocused_: Object, - - /** @private {boolean} */ omniboxExtensionListBlurred_: Boolean, - /** @private {?SearchEngine} */ dialogModel_: { type: Object, value: null, }, - /** @private {?HTMLElement} */ dialogAnchorElement_: { type: Object, value: null, }, - /** @private */ showDialog_: { type: Boolean, value: false, }, - /** @private */ showKeywordTriggerSetting_: { type: Boolean, value: () => loadTimeData.getBoolean('showKeywordTriggerSetting'), }, - /** @private */ showActiveSearchEngines_: { type: Boolean, value: () => loadTimeData.getBoolean('showActiveSearchEngines'), }, - }; } @@ -168,7 +148,24 @@ return ['extensionsChanged_(extensions, showExtensionsList_)']; } - /** @override */ + defaultEngines: Array<SearchEngine>; + activeEngines: Array<SearchEngine>; + otherEngines: Array<SearchEngine>; + extensions: Array<SearchEngine>; + private showExtensionsList_: boolean; + filter: string; + private matchingDefaultEngines_: Array<SearchEngine>; + private matchingActiveEngines_: Array<SearchEngine>; + private matchingOtherEngines_: Array<SearchEngine>; + private matchingExtensions_: Array<SearchEngine>; + private omniboxExtensionlastFocused_: HTMLElement; + private omniboxExtensionListBlurred_: boolean; + private dialogModel_: SearchEngine|null; + private dialogAnchorElement_: HTMLElement|null; + private showDialog_: boolean; + private showKeywordTriggerSetting_: boolean; + private showActiveSearchEngines_: boolean; + ready() { super.ready(); @@ -178,56 +175,40 @@ 'search-engines-changed', this.enginesChanged_.bind(this)); // Sets offset in iron-list that uses the page as a scrollTarget. - afterNextRender(this, function() { + afterNextRender(this, () => { this.$.otherEngines.scrollOffset = this.$.otherEngines.offsetTop; }); this.addEventListener( 'edit-search-engine', - e => this.onEditSearchEngine_( - /** @type {!SearchEngineEditEvent} */ (e))); + e => this.onEditSearchEngine_(e as SearchEngineEditEvent)); } - /** - * @param {?SearchEngine} searchEngine - * @param {!HTMLElement} anchorElement - * @private - */ - openDialog_(searchEngine, anchorElement) { + private openDialog_( + searchEngine: SearchEngine|null, anchorElement: HTMLElement) { this.dialogModel_ = searchEngine; this.dialogAnchorElement_ = anchorElement; this.showDialog_ = true; } - /** @private */ - onCloseDialog_() { + private onCloseDialog_() { this.showDialog_ = false; - const anchor = /** @type {!HTMLElement} */ (this.dialogAnchorElement_); - focusWithoutInk(anchor); + focusWithoutInk(this.dialogAnchorElement_ as HTMLElement); this.dialogModel_ = null; this.dialogAnchorElement_ = null; } - /** - * @param {!SearchEngineEditEvent} e - * @private - */ - onEditSearchEngine_(e) { + private onEditSearchEngine_(e: SearchEngineEditEvent) { this.openDialog_(e.detail.engine, e.detail.anchorElement); } - /** @private */ - extensionsChanged_() { + private extensionsChanged_() { if (this.showExtensionsList_ && this.$.extensions) { - /** @type {!IronListElement} */ (this.$.extensions).notifyResize(); + this.$.extensions.notifyResize(); } } - /** - * @param {!SearchEnginesInfo} searchEnginesInfo - * @private - */ - enginesChanged_(searchEnginesInfo) { + private enginesChanged_(searchEnginesInfo: SearchEnginesInfo) { this.defaultEngines = searchEnginesInfo.defaults; // Sort |activeEngines| and |otherEngines| in alphabetical order. @@ -241,28 +222,20 @@ this.extensions = searchEnginesInfo.extensions; } - /** - * @param {!Event} e - * @private - */ - onAddSearchEngineTap_(e) { + private onAddSearchEngineTap_(e: Event) { e.preventDefault(); - this.openDialog_( - null, assert(/** @type {HTMLElement} */ (this.$.addSearchEngine))); + this.openDialog_(null, this.$.addSearchEngine); } - /** @private */ - computeShowExtensionsList_() { + private computeShowExtensionsList_(): boolean { return this.extensions.length > 0; } /** * Filters the given list based on the currently existing filter string. - * @param {!Array<!SearchEngine>} list - * @return {!Array<!SearchEngine>} - * @private */ - computeMatchingEngines_(list) { + private computeMatchingEngines_(list: Array<SearchEngine>): + Array<SearchEngine> { if (this.filter === '') { return list; } @@ -275,12 +248,12 @@ } /** - * @param {!Array<!SearchEngine>} list The original list. - * @param {!Array<!SearchEngine>} filteredList The filtered list. - * @return {boolean} Whether to show the "no results" message. - * @private + * @param list The original list. + * @param filteredList The filtered list. + * @return Whether to show the "no results" message. */ - showNoResultsMessage_(list, filteredList) { + private showNoResultsMessage_( + list: Array<SearchEngine>, filteredList: Array<SearchEngine>): boolean { return list.length > 0 && filteredList.length === 0; } }
diff --git a/chrome/browser/resources/settings/search_page/BUILD.gn b/chrome/browser/resources/settings/search_page/BUILD.gn index a9d3bdc9..9d88e41 100644 --- a/chrome/browser/resources/settings/search_page/BUILD.gn +++ b/chrome/browser/resources/settings/search_page/BUILD.gn
@@ -2,28 +2,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//third_party/closure_compiler/compile_js.gni") import("//tools/polymer/html_to_js.gni") -import("../settings.gni") - -js_type_check("closure_compile") { - is_polymer3 = true - closure_flags = settings_closure_flags - deps = [ ":search_page" ] -} - -js_library("search_page") { - deps = [ - "..:base_mixin", - "..:route", - "..:router", - "../search_engines_page:search_engines_browser_proxy", - "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", - "//ui/webui/resources/js:cr.m", - ] - externs_list = [ "$externs_path/settings_private.js" ] -} html_to_js("web_components") { - js_files = [ "search_page.js" ] + js_files = [ "search_page.ts" ] }
diff --git a/chrome/browser/resources/settings/search_page/search_page.js b/chrome/browser/resources/settings/search_page/search_page.ts similarity index 74% rename from chrome/browser/resources/settings/search_page/search_page.js rename to chrome/browser/resources/settings/search_page/search_page.ts index 20f2337..b227481a8 100644 --- a/chrome/browser/resources/settings/search_page/search_page.js +++ b/chrome/browser/resources/settings/search_page/search_page.ts
@@ -24,7 +24,7 @@ import {BaseMixin, BaseMixinInterface} from '../base_mixin.js'; import {routes} from '../route.js'; import {Router} from '../router.js'; -import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl} from '../search_engines_page/search_engines_browser_proxy.js'; +import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl, SearchEnginesInfo} from '../search_engines_page/search_engines_browser_proxy.js'; /** @@ -33,7 +33,8 @@ * @appliesMixin {BaseMixin} * @implements {BaseMixinInterface} */ -const SettingsSearchPageElementBase = BaseMixin(PolymerElement); +const SettingsSearchPageElementBase = + BaseMixin(PolymerElement) as unknown as {new (): PolymerElement}; /** @polymer */ export class SettingsSearchPageElement extends SettingsSearchPageElementBase { @@ -51,7 +52,6 @@ /** * List of default search engines available. - * @private {!Array<!SearchEngine>} */ searchEngines_: { type: Array, @@ -60,27 +60,25 @@ } }, - /** @private Filter applied to search engines. */ + /** Filter applied to search engines. */ searchEnginesFilter_: String, - /** @type {?Map<string, string>} */ focusConfig_: Object, }; } - constructor() { - super(); - - /** @private {!SearchEnginesBrowserProxy} */ - this.browserProxy_ = SearchEnginesBrowserProxyImpl.getInstance(); - } + private searchEngines_: Array<SearchEngine>; + private searchEnginesFilter_: string; + private focusConfig_: Map<string, string>|null; + private browserProxy_: SearchEnginesBrowserProxy = + SearchEnginesBrowserProxyImpl.getInstance(); /** @override */ ready() { super.ready(); // Omnibox search engine - const updateSearchEngines = searchEngines => { + const updateSearchEngines = (searchEngines: SearchEnginesInfo) => { this.set('searchEngines_', searchEngines.defaults); }; this.browserProxy_.getSearchEnginesList().then(updateSearchEngines); @@ -93,16 +91,13 @@ } } - /** @private */ - onChange_() { - const select = /** @type {!HTMLSelectElement} */ ( - this.shadowRoot.querySelector('select')); + private onChange_() { + const select = this.shadowRoot!.querySelector('select')!; const searchEngine = this.searchEngines_[select.selectedIndex]; this.browserProxy_.setDefaultSearchEngine(searchEngine.modelIndex); } - /** @private */ - onDisableExtension_() { + private onDisableExtension_() { this.dispatchEvent(new CustomEvent('refresh-pref', { bubbles: true, composed: true, @@ -110,27 +105,18 @@ })); } - /** @private */ - onManageSearchEnginesTap_() { + private onManageSearchEnginesTap_() { Router.getInstance().navigateTo(routes.SEARCH_ENGINES); } - /** - * @param {!chrome.settingsPrivate.PrefObject} pref - * @return {boolean} - * @private - */ - isDefaultSearchControlledByPolicy_(pref) { + private isDefaultSearchControlledByPolicy_( + pref: chrome.settingsPrivate.PrefObject): boolean { return pref.controlledBy === chrome.settingsPrivate.ControlledBy.USER_POLICY; } - /** - * @param {!chrome.settingsPrivate.PrefObject} pref - * @return {boolean} - * @private - */ - isDefaultSearchEngineEnforced_(pref) { + private isDefaultSearchEngineEnforced_( + pref: chrome.settingsPrivate.PrefObject): boolean { return pref.enforcement === chrome.settingsPrivate.Enforcement.ENFORCED; } }
diff --git a/chrome/browser/resources/settings/settings_page_css.ts b/chrome/browser/resources/settings/settings_page_css.ts index ddfa4807..a498575e 100644 --- a/chrome/browser/resources/settings/settings_page_css.ts +++ b/chrome/browser/resources/settings/settings_page_css.ts
@@ -7,3 +7,4 @@ <dom-module id="settings-page-styles">{__html_template__}</dom-module> `; document.body.appendChild(template.content.cloneNode(true)); +export {};
diff --git a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc index 6146346..f88d9e52 100644 --- a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc +++ b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
@@ -239,7 +239,6 @@ weakptr_factory_.GetWeakPtr()), LaunchFileUtilService()); dmg_analyzer_->Start(); - dmg_analysis_start_time_ = base::TimeTicks::Now(); } void FileAnalyzer::ExtractFileOrDmgFeatures( @@ -278,9 +277,6 @@ results_.type = ClientDownloadRequest::MAC_ARCHIVE_FAILED_PARSING; } - UMA_HISTOGRAM_MEDIUM_TIMES("SBClientDownload.ExtractDmgFeaturesTimeMedium", - base::TimeTicks::Now() - dmg_analysis_start_time_); - std::move(callback_).Run(std::move(results_)); } #endif // defined(OS_MAC)
diff --git a/chrome/browser/safe_browsing/download_protection/file_analyzer.h b/chrome/browser/safe_browsing/download_protection/file_analyzer.h index 37dbffe..939fd40 100644 --- a/chrome/browser/safe_browsing/download_protection/file_analyzer.h +++ b/chrome/browser/safe_browsing/download_protection/file_analyzer.h
@@ -117,7 +117,6 @@ #if defined(OS_MAC) scoped_refptr<SandboxedDMGAnalyzer> dmg_analyzer_; - base::TimeTicks dmg_analysis_start_time_; #endif base::WeakPtrFactory<FileAnalyzer> weakptr_factory_{this};
diff --git a/chrome/browser/sharesheet/drive_share_action.cc b/chrome/browser/sharesheet/drive_share_action.cc index 3defccf..61729e1 100644 --- a/chrome/browser/sharesheet/drive_share_action.cc +++ b/chrome/browser/sharesheet/drive_share_action.cc
@@ -9,6 +9,8 @@ #include "base/strings/utf_string_conversions.h" #include "chrome/app/vector_icons/vector_icons.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sharesheet/sharesheet_controller.h" #include "chrome/browser/sharesheet/sharesheet_types.h" #include "chrome/browser/ui/browser_navigator.h" #include "chrome/browser/ui/browser_navigator_params.h" @@ -21,7 +23,7 @@ namespace sharesheet { -DriveShareAction::DriveShareAction() = default; +DriveShareAction::DriveShareAction(Profile* profile) : profile_(profile) {} DriveShareAction::~DriveShareAction() = default; @@ -38,8 +40,7 @@ apps::mojom::IntentPtr intent) { controller_ = controller; DCHECK(intent->drive_share_url.has_value()); - NavigateParams params(controller_->GetProfile(), - intent->drive_share_url.value(), + NavigateParams params(profile_, intent->drive_share_url.value(), ui::PAGE_TRANSITION_LINK); params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; Navigate(¶ms);
diff --git a/chrome/browser/sharesheet/drive_share_action.h b/chrome/browser/sharesheet/drive_share_action.h index 39338f8..0daede5 100644 --- a/chrome/browser/sharesheet/drive_share_action.h +++ b/chrome/browser/sharesheet/drive_share_action.h
@@ -7,11 +7,13 @@ #include "chrome/browser/sharesheet/share_action.h" +class Profile; + namespace sharesheet { class DriveShareAction : public ShareAction { public: - DriveShareAction(); + explicit DriveShareAction(Profile* profile); ~DriveShareAction() override; DriveShareAction(const DriveShareAction&) = delete; DriveShareAction& operator=(const DriveShareAction&) = delete; @@ -27,6 +29,7 @@ bool contains_hosted_document) override; private: + Profile* profile_; SharesheetController* controller_ = nullptr; };
diff --git a/chrome/browser/sharesheet/example_action.cc b/chrome/browser/sharesheet/example_action.cc index a54c729..e5c5212 100644 --- a/chrome/browser/sharesheet/example_action.cc +++ b/chrome/browser/sharesheet/example_action.cc
@@ -3,8 +3,10 @@ // found in the LICENSE file. #include "chrome/browser/sharesheet/example_action.h" + #include "base/strings/utf_string_conversions.h" #include "chrome/app/vector_icons/vector_icons.h" +#include "chrome/browser/sharesheet/sharesheet_controller.h" #include "chrome/browser/sharesheet/sharesheet_types.h" #include "components/services/app_service/public/mojom/types.mojom.h"
diff --git a/chrome/browser/sharesheet/share_action.h b/chrome/browser/sharesheet/share_action.h index 18584ef..349284d 100644 --- a/chrome/browser/sharesheet/share_action.h +++ b/chrome/browser/sharesheet/share_action.h
@@ -8,17 +8,18 @@ #include <string> #include "base/callback.h" -#include "chrome/browser/sharesheet/sharesheet_controller.h" #include "components/services/app_service/public/mojom/types.mojom.h" #include "ui/base/accelerators/accelerator.h" #include "ui/views/view.h" namespace gfx { struct VectorIcon; -} +} // namespace gfx namespace sharesheet { +class SharesheetController; + // An interface implemented by each ShareAction. class ShareAction { public:
diff --git a/chrome/browser/sharesheet/sharesheet_action_cache.cc b/chrome/browser/sharesheet/sharesheet_action_cache.cc index 6150a18d..1569ae3 100644 --- a/chrome/browser/sharesheet/sharesheet_action_cache.cc +++ b/chrome/browser/sharesheet/sharesheet_action_cache.cc
@@ -26,9 +26,9 @@ #if BUILDFLAG(IS_CHROMEOS_ASH) if (NearbySharingServiceFactory::IsNearbyShareSupportedForBrowserContext( profile)) { - AddShareAction(std::make_unique<NearbyShareAction>()); + AddShareAction(std::make_unique<NearbyShareAction>(profile)); } - AddShareAction(std::make_unique<DriveShareAction>()); + AddShareAction(std::make_unique<DriveShareAction>(profile)); #endif // BUILDFLAG(IS_CHROMEOS_ASH) }
diff --git a/chrome/browser/sharesheet/sharesheet_controller.h b/chrome/browser/sharesheet/sharesheet_controller.h index 02a217f..cd7a640 100644 --- a/chrome/browser/sharesheet/sharesheet_controller.h +++ b/chrome/browser/sharesheet/sharesheet_controller.h
@@ -7,8 +7,6 @@ #include "chrome/browser/sharesheet/sharesheet_types.h" -class Profile; - namespace sharesheet { // The SharesheetController allows ShareActions to request changes to the state @@ -17,8 +15,6 @@ public: virtual ~SharesheetController() = default; - virtual Profile* GetProfile() = 0; - // When called will set the bubble size to |width| and |height|. // |width| and |height| must be set to a positive int. virtual void SetBubbleSize(int width, int height) = 0;
diff --git a/chrome/browser/sharesheet/sharesheet_service.cc b/chrome/browser/sharesheet/sharesheet_service.cc index ace63b1b..b3feb7c 100644 --- a/chrome/browser/sharesheet/sharesheet_service.cc +++ b/chrome/browser/sharesheet/sharesheet_service.cc
@@ -15,6 +15,7 @@ #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" #include "chrome/browser/apps/app_service/launch_utils.h" #include "chrome/browser/sharesheet/share_action.h" +#include "chrome/browser/sharesheet/sharesheet_service_delegate.h" #include "chrome/browser/sharesheet/sharesheet_types.h" #include "chrome/common/chrome_features.h" #include "chrome/grit/generated_resources.h" @@ -26,10 +27,7 @@ #include "ui/views/view.h" #if BUILDFLAG(IS_CHROMEOS_ASH) -#include "chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.h" #include "ui/chromeos/strings/grit/ui_chromeos_strings.h" -#else -#include "chrome/browser/sharesheet/sharesheet_service_delegate.h" #endif // BUILDFLAG(IS_CHROMEOS_ASH) namespace sharesheet { @@ -126,7 +124,7 @@ ShareAction* share_action = sharesheet_action_cache_->GetActionFromName(active_action); if (share_action != nullptr) - share_action->OnClosing(iter->get()); + share_action->OnClosing(iter->get()->GetSharesheetController()); } active_delegates_.erase(iter); break; @@ -151,7 +149,8 @@ if (share_action == nullptr) return; delegate->OnActionLaunched(); - share_action->LaunchAction(delegate, share_action_view, std::move(intent)); + share_action->LaunchAction(delegate->GetSharesheetController(), + share_action_view, std::move(intent)); } else if (type == TargetType::kArcApp || type == TargetType::kWebApp) { DCHECK(intent); LaunchApp(target_name, std::move(intent)); @@ -177,12 +176,7 @@ auto* delegate = GetDelegate(native_window); if (delegate == nullptr) { auto new_delegate = -#if BUILDFLAG(IS_CHROMEOS_ASH) - std::make_unique<ash::sharesheet::CrosSharesheetServiceDelegate>( - native_window, this); -#else std::make_unique<SharesheetServiceDelegate>(native_window, this); -#endif // BUILDFLAG(IS_CHROMEOS_ASH) delegate = new_delegate.get(); active_delegates_.push_back(std::move(new_delegate)); }
diff --git a/chrome/browser/sharesheet/sharesheet_service_delegate.cc b/chrome/browser/sharesheet/sharesheet_service_delegate.cc index 003508f..51bdfa9 100644 --- a/chrome/browser/sharesheet/sharesheet_service_delegate.cc +++ b/chrome/browser/sharesheet/sharesheet_service_delegate.cc
@@ -4,8 +4,12 @@ #include "chrome/browser/sharesheet/sharesheet_service_delegate.h" +#include <utility> + +#include "base/callback.h" #include "chrome/browser/sharesheet/sharesheet_service.h" -#include "chrome/browser/sharesheet/sharesheet_types.h" +#include "chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h" +#include "ui/base/accelerators/accelerator.h" #include "ui/views/view.h" namespace sharesheet { @@ -13,22 +17,60 @@ SharesheetServiceDelegate::SharesheetServiceDelegate( gfx::NativeWindow native_window, SharesheetService* sharesheet_service) - : native_window_(native_window), sharesheet_service_(sharesheet_service) {} + : native_window_(native_window), sharesheet_service_(sharesheet_service) { +#if BUILDFLAG(IS_CHROMEOS_ASH) + sharesheet_controller_ = + std::make_unique<ash::sharesheet::SharesheetBubbleViewDelegate>( + native_window_, this); +#else + NOTIMPLEMENTED(); +#endif // BUILDFLAG(IS_CHROMEOS_ASH) +} + +SharesheetServiceDelegate::~SharesheetServiceDelegate() = default; gfx::NativeWindow SharesheetServiceDelegate::GetNativeWindow() { return native_window_; } -void SharesheetServiceDelegate::ShowBubble( - std::vector<TargetInfo> targets, - apps::mojom::IntentPtr intent, - sharesheet::DeliveredCallback delivered_callback, - sharesheet::CloseCallback close_callback) { - NOTIMPLEMENTED(); +SharesheetController* SharesheetServiceDelegate::GetSharesheetController() { + return sharesheet_controller_.get(); } +Profile* SharesheetServiceDelegate::GetProfile() { + return sharesheet_service_->GetProfile(); +} + +void SharesheetServiceDelegate::ShowBubble(std::vector<TargetInfo> targets, + apps::mojom::IntentPtr intent, + DeliveredCallback delivered_callback, + CloseCallback close_callback) { + sharesheet_controller_->ShowBubble(std::move(targets), std::move(intent), + std::move(delivered_callback), + std::move(close_callback)); +} + +#if BUILDFLAG(IS_CHROMEOS_ASH) +// Skips the generic Sharesheet bubble and directly displays the +// NearbyShare bubble dialog. +void SharesheetServiceDelegate::ShowNearbyShareBubbleForArc( + apps::mojom::IntentPtr intent, + DeliveredCallback delivered_callback, + CloseCallback close_callback) { + sharesheet_controller_->ShowNearbyShareBubbleForArc( + std::move(intent), std::move(delivered_callback), + std::move(close_callback)); +} +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + +// Invoked immediately after an action has launched in the event that UI +// changes need to occur at this point. void SharesheetServiceDelegate::OnActionLaunched() { - NOTIMPLEMENTED(); + sharesheet_controller_->OnActionLaunched(); +} + +void SharesheetServiceDelegate::CloseBubble(SharesheetResult result) { + sharesheet_controller_->CloseBubble(result); } void SharesheetServiceDelegate::OnBubbleClosed( @@ -57,12 +99,4 @@ return sharesheet_service_->GetVectorIcon(display_name); } -Profile* SharesheetServiceDelegate::GetProfile() { - return sharesheet_service_->GetProfile(); -} - -void SharesheetServiceDelegate::SetBubbleSize(int width, int height) {} - -void SharesheetServiceDelegate::CloseBubble(SharesheetResult result) {} - } // namespace sharesheet
diff --git a/chrome/browser/sharesheet/sharesheet_service_delegate.h b/chrome/browser/sharesheet/sharesheet_service_delegate.h index e56b817b..8f16d39 100644 --- a/chrome/browser/sharesheet/sharesheet_service_delegate.h +++ b/chrome/browser/sharesheet/sharesheet_service_delegate.h
@@ -5,11 +5,12 @@ #ifndef CHROME_BROWSER_SHARESHEET_SHARESHEET_SERVICE_DELEGATE_H_ #define CHROME_BROWSER_SHARESHEET_SHARESHEET_SERVICE_DELEGATE_H_ +#include <memory> #include <string> #include "base/callback.h" -#include "chrome/browser/sharesheet/sharesheet_controller.h" #include "chrome/browser/sharesheet/sharesheet_types.h" +#include "chrome/browser/sharesheet/sharesheet_ui_delegate.h" #include "components/services/app_service/public/mojom/types.mojom.h" #include "ui/base/accelerators/accelerator.h" #include "ui/gfx/native_widget_types.h" @@ -20,6 +21,10 @@ struct VectorIcon; } // namespace gfx +namespace ui { +class Accelerator; +} // namespace ui + namespace views { class View; } // namespace views @@ -30,34 +35,49 @@ // The SharesheetServiceDelegate is the interface through which the business // logic in SharesheetService communicates with the UI. -class SharesheetServiceDelegate : public ::sharesheet::SharesheetController { +class SharesheetServiceDelegate { public: SharesheetServiceDelegate(gfx::NativeWindow native_window, SharesheetService* sharesheet_service); - ~SharesheetServiceDelegate() override = default; + ~SharesheetServiceDelegate(); SharesheetServiceDelegate(const SharesheetServiceDelegate&) = delete; SharesheetServiceDelegate& operator=(const SharesheetServiceDelegate&) = delete; gfx::NativeWindow GetNativeWindow(); + SharesheetController* GetSharesheetController(); + + // TODO(crbug.com/1233830) : Remove after business logic is moved + // out of SharesheetHeaderView. + Profile* GetProfile(); + + // ========================================================================== + // ======================== SHARESHEET SERVICE TO UI ======================== + // ========================================================================== // The following are called by the ShareService to communicate with the UI. - virtual void ShowBubble(std::vector<TargetInfo> targets, - apps::mojom::IntentPtr intent, - sharesheet::DeliveredCallback delivered_callback, - sharesheet::CloseCallback close_callback); + void ShowBubble(std::vector<TargetInfo> targets, + apps::mojom::IntentPtr intent, + DeliveredCallback delivered_callback, + CloseCallback close_callback); #if BUILDFLAG(IS_CHROMEOS_ASH) - virtual void ShowNearbyShareBubbleForArc( - apps::mojom::IntentPtr intent, - sharesheet::DeliveredCallback delivered_callback, - sharesheet::CloseCallback close_callback) = 0; + // Skips the generic Sharesheet bubble and directly displays the + // NearbyShare bubble dialog. + void ShowNearbyShareBubbleForArc(apps::mojom::IntentPtr intent, + DeliveredCallback delivered_callback, + CloseCallback close_callback); #endif // BUILDFLAG(IS_CHROMEOS_ASH) // Invoked immediately after an action has launched in the event that UI // changes need to occur at this point. - virtual void OnActionLaunched(); + void OnActionLaunched(); + void CloseBubble(SharesheetResult result); + + // ========================================================================== + // ======================== UI TO SHARESHEET SERVICE ======================== + // ========================================================================== // The following are called by the UI to communicate with the ShareService. void OnBubbleClosed(const std::u16string& active_action); void OnTargetSelected(const std::u16string& target_name, @@ -68,20 +88,14 @@ const std::u16string& active_action); const gfx::VectorIcon* GetVectorIcon(const std::u16string& display_name); - // SharesheetController: - Profile* GetProfile() override; - // Default implementation does nothing. Override as needed. - void SetBubbleSize(int width, int height) override; - // Default implementation does nothing. Override as needed. - void CloseBubble(SharesheetResult result) override; - private: // Only used for ID purposes. NativeWindow will always outlive the // SharesheetServiceDelegate. gfx::NativeWindow native_window_; - // Owned by views. SharesheetService* sharesheet_service_; + + std::unique_ptr<SharesheetUiDelegate> sharesheet_controller_; }; } // namespace sharesheet
diff --git a/chrome/browser/sharesheet/sharesheet_ui_delegate.h b/chrome/browser/sharesheet/sharesheet_ui_delegate.h new file mode 100644 index 0000000..b42fa06 --- /dev/null +++ b/chrome/browser/sharesheet/sharesheet_ui_delegate.h
@@ -0,0 +1,43 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SHARESHEET_SHARESHEET_UI_DELEGATE_H_ +#define CHROME_BROWSER_SHARESHEET_SHARESHEET_UI_DELEGATE_H_ + +#include <vector> + +#include "chrome/browser/sharesheet/sharesheet_controller.h" +#include "chrome/browser/sharesheet/sharesheet_types.h" +#include "components/services/app_service/public/mojom/types.mojom.h" +#include "ui/gfx/native_widget_types.h" + +namespace sharesheet { + +// The SharesheetUiDelegate is the interface for the controller used by +// the ShareService to request changes to the UI. This class needs to be +// implemented by each share bubble. +class SharesheetUiDelegate : public SharesheetController { + public: + ~SharesheetUiDelegate() override = default; + + virtual void ShowBubble(std::vector<TargetInfo> targets, + apps::mojom::IntentPtr intent, + DeliveredCallback delivered_callback, + CloseCallback close_callback) = 0; +#if BUILDFLAG(IS_CHROMEOS_ASH) + // Skips the generic Sharesheet bubble and directly displays the + // NearbyShare bubble dialog. + virtual void ShowNearbyShareBubbleForArc(apps::mojom::IntentPtr intent, + DeliveredCallback delivered_callback, + CloseCallback close_callback) = 0; +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + + // Invoked immediately after an action has launched in the event that UI + // changes need to occur at this point. + virtual void OnActionLaunched() {} +}; + +} // namespace sharesheet + +#endif // CHROME_BROWSER_SHARESHEET_SHARESHEET_UI_DELEGATE_H_
diff --git a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java index 97546d7..0f289ed 100644 --- a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java +++ b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java
@@ -82,7 +82,7 @@ public class AccountPickerBottomSheetTest { private static class CustomFakeAccountInfoService extends FakeAccountInfoService { int getNumberOfObservers() { - return mObservers.size(); + return TestThreadUtils.runOnUiThreadBlockingNoException(mObservers::size); } }
diff --git a/chrome/browser/ssl/https_defaulted_callbacks.cc b/chrome/browser/ssl/https_defaulted_callbacks.cc index d3ec8210..d771ae6 100644 --- a/chrome/browser/ssl/https_defaulted_callbacks.cc +++ b/chrome/browser/ssl/https_defaulted_callbacks.cc
@@ -4,16 +4,18 @@ #include "chrome/browser/ssl/https_defaulted_callbacks.h" +#include "chrome/browser/profiles/profile.h" #include "chrome/browser/ssl/https_only_mode_tab_helper.h" #include "chrome/browser/ssl/typed_navigation_upgrade_throttle.h" #include "chrome/common/chrome_features.h" #include "components/omnibox/common/omnibox_features.h" +#include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/web_contents.h" #include "url/gurl.h" #include "url/url_constants.h" -bool ShouldIgnoreInterstitialBecauseNavigationDefaultedToHttps( +bool ShouldIgnoreSslInterstitialBecauseNavigationDefaultedToHttps( content::NavigationHandle* handle) { DCHECK_EQ(url::kHttpsScheme, handle->GetURL().scheme()); @@ -25,12 +27,31 @@ } // Check HTTPS-Only Mode upgrade status. - auto* https_only_mode_helper = - HttpsOnlyModeTabHelper::FromWebContents(handle->GetWebContents()); - if (base::FeatureList::IsEnabled(features::kHttpsOnlyMode) && - https_only_mode_helper->is_navigation_upgraded()) { - return true; + // Suppress any SSL errors if the navigation was upgraded to HTTPS by + // HTTPS-First Mode but it has not yet fallen back to HTTP. If the user + // already clicked through the HTTPS-First Mode interstitial then the SSL + // error should no longer be suppressed. + if (!base::FeatureList::IsEnabled(features::kHttpsOnlyMode)) { + return false; } - return false; + auto* https_only_mode_helper = + HttpsOnlyModeTabHelper::FromWebContents(handle->GetWebContents()); + bool is_upgraded = https_only_mode_helper && + https_only_mode_helper->is_navigation_upgraded(); + + Profile* profile = Profile::FromBrowserContext( + handle->GetWebContents()->GetBrowserContext()); + if (!profile) { + // In the edge case of there not being a Profile associated with this + // navigation, fail safe by not suppressing SSL interstitials. + return false; + } + StatefulSSLHostStateDelegate* state = + static_cast<StatefulSSLHostStateDelegate*>( + profile->GetSSLHostStateDelegate()); + bool is_allowlisted = + state && state->IsHttpAllowedForHost(handle->GetURL().host()); + + return is_upgraded && !is_allowlisted; }
diff --git a/chrome/browser/ssl/https_defaulted_callbacks.h b/chrome/browser/ssl/https_defaulted_callbacks.h index a34b7e9..551d074 100644 --- a/chrome/browser/ssl/https_defaulted_callbacks.h +++ b/chrome/browser/ssl/https_defaulted_callbacks.h
@@ -14,7 +14,7 @@ // it should not trigger SSL error interstitials. These features that upgrade // navigations to HTTPS have special handling for error cases -- see // `TypedNavigationUpgradeThrottle` and `HttpsOnlyModeNavigationThrottle`. -bool ShouldIgnoreInterstitialBecauseNavigationDefaultedToHttps( +bool ShouldIgnoreSslInterstitialBecauseNavigationDefaultedToHttps( content::NavigationHandle* handle); #endif // CHROME_BROWSER_SSL_HTTPS_DEFAULTED_CALLBACKS_H_
diff --git a/chrome/browser/ssl/https_only_mode_browsertest.cc b/chrome/browser/ssl/https_only_mode_browsertest.cc index 5c58404..ed47f9c 100644 --- a/chrome/browser/ssl/https_only_mode_browsertest.cc +++ b/chrome/browser/ssl/https_only_mode_browsertest.cc
@@ -442,3 +442,62 @@ ProceedThroughInterstitial(contents); EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel()); } + +// Regression test for crbug.com/1233207. +// Tests the case where the HTTP version of a site redirects to HTTPS, but the +// HTTPS version of the site has a cert error. If the user initially navigates +// to the HTTP URL, then HTTPS-First Mode should upgrade the navigation to HTTPS +// and trigger the HTTPS-First Mode interstitial when that fails, but if the +// user clicks through the HTTPS-First Mode interstitial and falls back into the +// HTTP->HTTPS redirect back to the cert error, then the SSL interstitial should +// be shown and the user should be able to click through the SSL interstitial to +// visit the HTTPS version of the site (but in a DANGEROUS security level +// state). +IN_PROC_BROWSER_TEST_F(HttpsOnlyModeBrowserTest, + HttpsUpgradeWithBrokenSSL_ShouldTriggerSSLInterstitial) { + // Set up a new test server instance so it can have a custom handler that + // redirects to the HTTPS server. + net::EmbeddedTestServer upgrading_server{net::EmbeddedTestServer::TYPE_HTTP}; + upgrading_server.RegisterRequestHandler(base::BindLambdaForTesting( + [&](const net::test_server::HttpRequest& request) + -> std::unique_ptr<net::test_server::HttpResponse> { + auto response = std::make_unique<net::test_server::BasicHttpResponse>(); + response->set_code(net::HTTP_TEMPORARY_REDIRECT); + response->AddCustomHeader( + "Location", + "https://bad-https.test:" + + base::NumberToString( + HttpsOnlyModeUpgradeInterceptor::GetHttpsPortForTesting()) + + "/simple.html"); + return response; + })); + HttpsOnlyModeUpgradeInterceptor::SetHttpPortForTesting( + upgrading_server.port()); + ASSERT_TRUE(upgrading_server.Start()); + + GURL http_url = upgrading_server.GetURL("bad-https.test", "/simple.html"); + // HTTPS server will have a cert error. + GURL https_url = https_server()->GetURL("bad-https.test", "/simple.html"); + + auto* contents = browser()->tab_strip_model()->GetActiveWebContents(); + EXPECT_FALSE(content::NavigateToURL(contents, http_url)); + EXPECT_EQ(https_url, contents->GetLastCommittedURL()); + + // The HTTPS-First Mode interstitial should trigger first. + EXPECT_TRUE(chrome_browser_interstitials::IsInterstitialDisplayingText( + contents->GetMainFrame(), + l10n_util::GetStringUTF8(IDS_HTTPS_ONLY_MODE_PRIMARY_PARAGRAPH))); + + // Proceeding through the HTTPS-First Mode interstitial will hit the upgrading + // server's HTTP->HTTPS redirect. This should result in an SSL interstitial + // (not an HTTPS-First Mode interstitial). + ProceedThroughInterstitial(contents); + EXPECT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents)); + + // Proceeding through the SSL interstitial should navigate to the HTTPS + // version of the site but with the DANGEROUS security level. + ProceedThroughInterstitial(contents); + EXPECT_EQ(https_url, contents->GetLastCommittedURL()); + auto* helper = SecurityStateTabHelper::FromWebContents(contents); + EXPECT_EQ(security_state::DANGEROUS, helper->GetSecurityLevel()); +}
diff --git a/chrome/browser/ssl/ssl_prerender_browsertest.cc b/chrome/browser/ssl/ssl_prerender_browsertest.cc new file mode 100644 index 0000000..cae9fe2 --- /dev/null +++ b/chrome/browser/ssl/ssl_prerender_browsertest.cc
@@ -0,0 +1,192 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <memory> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "chrome/browser/interstitials/security_interstitial_page_test_utils.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/ssl_host_state_delegate.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/prerender_test_util.h" +#include "content/public/test/url_loader_interceptor.h" +#include "net/test/cert_test_util.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using chrome_browser_interstitials::IsShowingSSLInterstitial; +using content::EvalJs; +using content::RenderFrameHost; +using content::SSLHostStateDelegate; +using content::TestNavigationManager; +using content::URLLoaderInterceptor; +using content::WebContents; +using net::EmbeddedTestServer; +using ui_test_utils::NavigateToURL; + +namespace { + +std::unique_ptr<net::EmbeddedTestServer> CreateExpiredCertServer( + const base::FilePath& data_dir) { + auto server = + std::make_unique<EmbeddedTestServer>(EmbeddedTestServer::TYPE_HTTPS); + server->SetSSLConfig(EmbeddedTestServer::CERT_EXPIRED); + server->ServeFilesFromSourceDirectory(data_dir); + return server; +} + +} // namespace + +class SSLPrerenderTest : public InProcessBrowserTest { + public: + SSLPrerenderTest() + : prerender_helper_(base::BindRepeating(&SSLPrerenderTest::web_contents, + base::Unretained(this))) {} + ~SSLPrerenderTest() override = default; + + protected: + content::WebContents* web_contents() { + return browser()->tab_strip_model()->GetActiveWebContents(); + } + + content::test::PrerenderTestHelper prerender_helper_; +}; + +// Verifies that a certificate error in a prerendered page causes cancelation +// of prerendering without showing an interstitial. +// TODO(bokan): In the future, when prerendering supports cross origin +// triggering, this test can be more straightforward by using one server for +// the initial page and another, with bad certs, for the prerendering page. +IN_PROC_BROWSER_TEST_F(SSLPrerenderTest, TestNoInterstitialInPrerender) { + auto server = CreateExpiredCertServer(GetChromeTestDataDir()); + ASSERT_TRUE(server->Start()); + + const GURL kPrerenderUrl = server->GetURL("/empty.html?prerender"); + const GURL kInitialUrl = server->GetURL("/empty.html"); + + // Use an interceptor to load the initial page. This is done because the + // server has certificate errors. If the initial URL is loaded from the test + // server, this will trigger an interstitial before the prerender can be + // triggered. Since the prerender must be same origin with the initial page, + // proceeding through that interstitial would add an exception for the URL, + // and so the error won't be visible to the prerender load. Since this test + // is trying to make sure that interstitials on prerender loads abort the + // prerender, this interceptor ensures the initial load won't have an + // interstitial, but the prerender will. + { + auto url_loader_interceptor = + content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin( + GetChromeTestDataDir().MaybeAsASCII(), kInitialUrl.GetOrigin()); + + // Navigate to the initial page. + NavigateToURL(browser(), kInitialUrl); + ASSERT_FALSE(IsShowingSSLInterstitial(web_contents())); + + // Make sure there is no exception for the prerendering URL, so that an SSL + // error will not be ignored. + Profile* profile = + Profile::FromBrowserContext(web_contents()->GetBrowserContext()); + SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate(); + ASSERT_FALSE( + state->HasAllowException(kPrerenderUrl.host(), web_contents())); + } + + // Trigger a prerender. Unlike the initial navigation, this will hit the + // server, so it'll respond with a bad certificate. If this request was a + // normal navigation, an interstitial would be shown, but because it is a + // prerender request, the prerender should be canceled and no interstitial + // shown. + { + TestNavigationManager observer(web_contents(), kPrerenderUrl); + + // Trigger the prerender. The PrerenderHost starts the request when it is + // created so it should be available after WaitForRequestStart. + prerender_helper_.AddPrerenderAsync(kPrerenderUrl); + ASSERT_TRUE(observer.WaitForRequestStart()); + ASSERT_NE(prerender_helper_.GetHostForUrl(kPrerenderUrl), + RenderFrameHost::kNoFrameTreeNodeId); + + // The prerender navigation should be canceled as part of the response. + // Ensure the prerender host is destroyed and no interstitial is showing. + EXPECT_FALSE(observer.WaitForResponse()); + EXPECT_EQ(prerender_helper_.GetHostForUrl(kPrerenderUrl), + RenderFrameHost::kNoFrameTreeNodeId); + EXPECT_FALSE(IsShowingSSLInterstitial(web_contents())); + } +} + +// Verifies that a certificate error in a prerendered page fetched via service +// worker causes cancelation of prerendering without showing an interstitial. +// TODO(bokan): In the future, when prerendering supports cross origin +// triggering, this test can be more straightforward by using one server for +// the initial page and another, with bad certs, for the prerendering page. +IN_PROC_BROWSER_TEST_F(SSLPrerenderTest, TestNoInterstitialInPrerenderSW) { + auto server = CreateExpiredCertServer(GetChromeTestDataDir()); + ASSERT_TRUE(server->Start()); + + const GURL kPrerenderUrl = server->GetURL("/service_worker/blank.html"); + const GURL kInitialUrl = + server->GetURL("/service_worker/create_service_worker.html"); + + // Use an interceptor to load the initial URL. This is done because the + // server has certificate errors. If the initial URL is loaded from the test + // server, this will trigger an interstitial before the prerender can be + // triggered. Since the prerender must be same origin with the initial page, + // proceeding through that interstitial would add an exception for the URL, + // and so the error won't be visible to the prerender load. Since this test + // is trying to make sure that interstitials on prerender loads abort the + // prerender, this interceptor ensures the initial load won't have an + // interstitial, but the prerender will. + { + auto url_loader_interceptor = + content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin( + GetChromeTestDataDir().MaybeAsASCII(), kInitialUrl.GetOrigin()); + + // Navigate to the initial page and register a service worker that will + // relay the fetch. + NavigateToURL(browser(), kInitialUrl); + ASSERT_EQ("DONE", EvalJs(web_contents(), + "register('fetch_event_respond_with_fetch.js');")); + ASSERT_FALSE(IsShowingSSLInterstitial(web_contents())); + + // Make sure there is no exception for the prerendering URL, so that an SSL + // error will not be ignored. + Profile* profile = + Profile::FromBrowserContext(web_contents()->GetBrowserContext()); + SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate(); + ASSERT_FALSE( + state->HasAllowException(kPrerenderUrl.host(), web_contents())); + } + + // Trigger a prerender. Unlike the initial navigation, this will hit the + // server, so it'll respond with a bad certificate. If this request was a + // normal navigation, an interstitial would be shown, but because it is a + // prerender request, the prerender should be canceled and no interstitial + // shown. + { + TestNavigationManager observer(web_contents(), kPrerenderUrl); + + // Trigger the prerender. The PrerenderHost starts the request when it is + // created so it should be available after WaitForRequestStart. + prerender_helper_.AddPrerenderAsync(kPrerenderUrl); + ASSERT_TRUE(observer.WaitForRequestStart()); + ASSERT_NE(prerender_helper_.GetHostForUrl(kPrerenderUrl), + RenderFrameHost::kNoFrameTreeNodeId); + + // The prerender navigation should be canceled as part of the response. + // Ensure the prerender host is destroyed and no interstitial is showing. + EXPECT_FALSE(observer.WaitForResponse()); + EXPECT_EQ(prerender_helper_.GetHostForUrl(kPrerenderUrl), + RenderFrameHost::kNoFrameTreeNodeId); + EXPECT_FALSE(IsShowingSSLInterstitial(web_contents())); + } +}
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModelTest.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModelTest.java index 07c8fb3..58b4dbd 100644 --- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModelTest.java +++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModelTest.java
@@ -73,7 +73,9 @@ public void testCloseAllDuringAddTabDoesNotCrash() { createTabOnUiThread(); Assert.assertEquals(1, mTabModel.getCount()); - mTabModel.addObserver(new CloseAllDuringAddTabTabModelObserver()); + TestThreadUtils.runOnUiThreadBlocking( + () -> mTabModel.addObserver(new CloseAllDuringAddTabTabModelObserver())); + createTabOnUiThread(); Assert.assertEquals(1, mTabModel.getCount()); } @@ -103,16 +105,18 @@ CallbackHelper didAddTabCallbackHelper = new CallbackHelper(); CallbackHelper tabRemovedCallbackHelper = new CallbackHelper(); - mTabModel.addObserver(new TabModelObserver() { - @Override - public void tabRemoved(Tab tab) { - tabRemovedCallbackHelper.notifyCalled(); - } + TestThreadUtils.runOnUiThreadBlocking(() -> { + mTabModel.addObserver(new TabModelObserver() { + @Override + public void tabRemoved(Tab tab) { + tabRemovedCallbackHelper.notifyCalled(); + } - @Override - public void didAddTab(Tab tab, int type, int creationState) { - didAddTabCallbackHelper.notifyCalled(); - } + @Override + public void didAddTab(Tab tab, int type, int creationState) { + didAddTabCallbackHelper.notifyCalled(); + } + }); }); createTabOnUiThread();
diff --git a/chrome/browser/translate/android/translate_bridge.cc b/chrome/browser/translate/android/translate_bridge.cc index 9164253..2fa6087 100644 --- a/chrome/browser/translate/android/translate_bridge.cc +++ b/chrome/browser/translate/android/translate_bridge.cc
@@ -97,11 +97,13 @@ } else { // We don't check for source_language_code support because TranslatePage // handles that case already. - manager->TranslatePage(source_language_code, target_language_code, - /*triggered_from_menu=*/false, - /*translation_type=*/ - manager->GetActiveTranslateMetricsLogger() - ->GetNextManualTranslationType()); + manager->TranslatePage( + source_language_code, target_language_code, + /*triggered_from_menu=*/false, + /*translation_type=*/ + manager->GetActiveTranslateMetricsLogger() + ->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); } }
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index 9a98777..4f2c3d7 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -2083,6 +2083,8 @@ "ash/session_util.h", "ash/sharesheet/sharesheet_bubble_view.cc", "ash/sharesheet/sharesheet_bubble_view.h", + "ash/sharesheet/sharesheet_bubble_view_delegate.cc", + "ash/sharesheet/sharesheet_bubble_view_delegate.h", "ash/sharesheet/sharesheet_constants.h", "ash/sharesheet/sharesheet_expand_button.cc", "ash/sharesheet/sharesheet_expand_button.h", @@ -2291,6 +2293,8 @@ "webui/chromeos/assistant_optin/assistant_optin_ui.h", "webui/chromeos/assistant_optin/assistant_optin_utils.cc", "webui/chromeos/assistant_optin/assistant_optin_utils.h", + "webui/chromeos/audio/audio_handler.cc", + "webui/chromeos/audio/audio_handler.h", "webui/chromeos/audio/audio_ui.cc", "webui/chromeos/audio/audio_ui.h", "webui/chromeos/bluetooth_pairing_dialog.cc", @@ -2772,6 +2776,7 @@ "//chrome/browser/ui/app_list/search/search_result_ranker:recurrence_ranker_proto", "//chrome/browser/ui/webui/app_management:mojo_bindings", "//chrome/browser/ui/webui/chromeos/add_supervision:mojo_bindings", + "//chrome/browser/ui/webui/chromeos/audio:mojo_bindings", "//chrome/browser/ui/webui/chromeos/crostini_upgrader:mojo_bindings", "//chrome/browser/ui/webui/chromeos/enterprise_casting:mojo_bindings", "//chrome/browser/ui/webui/chromeos/launcher_internals:mojo_bindings",
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd index 3d1710f..844bed9 100644 --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -2018,6 +2018,42 @@ You’ll see a notification when this download starts at <ph name="time">%1$s<ex>8:30PM</ex></ph>. </message> + <!-- Download progress message UI --> + <message name="IDS_DOWNLOAD_MESSAGE_DOWNLOAD_IN_PROGRESS_DESCRIPTION" desc="Download message text describing that user can find about their download status from notifications."> + You can see the download status in your notifications + </message> + <message name="IDS_DOWNLOAD_MESSAGE_DOWNLOAD_COMPLETE_DESCRIPTION" desc="Download message description text about downloaded file name and their size."> + (<ph name="megabytes">%1$s<ex>100 MB</ex></ph>)\n<ph name="url">%2$s<ex>https://...example.com</ex></ph> + </message> + <message name="IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_IN_PROGRESS" desc="Download message text describing that chrome is downloading multiple files."> + {FILE_COUNT, plural, + =1 {Downloading file…} + other {Downloading # files…}} + </message> + <message name="IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_COMPLETE" desc="Download message label describing that one or more downloads have completed"> + {FILE_COUNT, plural, + =1 {File downloaded} + other {# downloads complete}} + </message> + <message name="IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_FAILED" desc="Download message label describing that one or more downloads have failed"> + {FILE_COUNT, plural, + =1 {1 download failed} + other {# downloads failed}} + </message> + <message name="IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_PENDING" desc="Download message label describing that one or more downloads are pending"> + {FILE_COUNT, plural, + =1 {1 download pending} + other {# downloads pending}} + </message> + <message name="IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_SCHEDULED" desc="Download message label describing that one or more downloads are scheduled"> + {FILE_COUNT, plural, + =1 {1 download scheduled} + other {# downloads scheduled}} + </message> + <message name="IDS_DOWNLOAD_MESSAGE_DOWNLOAD_SCHEDULED_DESCRIPTION" desc="Download message text describing that user can find about their download status from notifications."> + Download will start when on Wi-Fi + </message> + <!-- Inline Update Infobar --> <message name="IDS_INLINE_UPDATE_INFOBAR_READY_MESSAGE" desc="Text showing that the update process is almost ready."> Almost finished!
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_COMPLETE_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_COMPLETE_DESCRIPTION.png.sha1 new file mode 100644 index 0000000..18a46f1 --- /dev/null +++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_COMPLETE_DESCRIPTION.png.sha1
@@ -0,0 +1 @@ +436eebd2b3a112e6556c98dc9c8d00ba618b5ead \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_IN_PROGRESS_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_IN_PROGRESS_DESCRIPTION.png.sha1 new file mode 100644 index 0000000..94a9732 --- /dev/null +++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_IN_PROGRESS_DESCRIPTION.png.sha1
@@ -0,0 +1 @@ +6c18810ecd01392a1a749d75490a1d2d619af5d1 \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_SCHEDULED_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_SCHEDULED_DESCRIPTION.png.sha1 new file mode 100644 index 0000000..57e5263 --- /dev/null +++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_SCHEDULED_DESCRIPTION.png.sha1
@@ -0,0 +1 @@ +615c3db108752ad5ccd1f6022e19ece79dcf992c \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_COMPLETE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_COMPLETE.png.sha1 new file mode 100644 index 0000000..2bfe1db --- /dev/null +++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_COMPLETE.png.sha1
@@ -0,0 +1 @@ +a320dadee28c239e3de9bc06e68a9856f90c7ffa \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_FAILED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_FAILED.png.sha1 new file mode 100644 index 0000000..1f4282e --- /dev/null +++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_FAILED.png.sha1
@@ -0,0 +1 @@ +480a8a6ea29a695efd32bbce890fd7b5966a89f4 \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_IN_PROGRESS.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_IN_PROGRESS.png.sha1 new file mode 100644 index 0000000..897ef61e --- /dev/null +++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_IN_PROGRESS.png.sha1
@@ -0,0 +1 @@ +373e2e982101719158b6a71dac294d861f73324d \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_PENDING.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_PENDING.png.sha1 new file mode 100644 index 0000000..1f4282e --- /dev/null +++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_PENDING.png.sha1
@@ -0,0 +1 @@ +480a8a6ea29a695efd32bbce890fd7b5966a89f4 \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_SCHEDULED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_SCHEDULED.png.sha1 new file mode 100644 index 0000000..f0a8844 --- /dev/null +++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_SCHEDULED.png.sha1
@@ -0,0 +1 @@ +795499cb3552abce6563e4ef6184487b3c1a7075 \ No newline at end of file
diff --git a/chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.cc similarity index 74% rename from chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.cc rename to chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.cc index 8613927..6acbd61 100644 --- a/chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.cc +++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.cc
@@ -2,25 +2,27 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.h" +#include "chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h" #include <utility> #include "base/bind.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sharesheet/sharesheet_service_delegate.h" #include "chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h" #include "ui/views/widget/widget.h" namespace ash { namespace sharesheet { -CrosSharesheetServiceDelegate::CrosSharesheetServiceDelegate( +SharesheetBubbleViewDelegate::SharesheetBubbleViewDelegate( gfx::NativeWindow native_window, - ::sharesheet::SharesheetService* sharesheet_service) - : ::sharesheet::SharesheetServiceDelegate(native_window, - sharesheet_service), - sharesheet_bubble_view_(new SharesheetBubbleView(native_window, this)) {} + ::sharesheet::SharesheetServiceDelegate* sharesheet_service_delegate) + : sharesheet_bubble_view_( + new SharesheetBubbleView(native_window, + sharesheet_service_delegate)) {} -void CrosSharesheetServiceDelegate::ShowBubble( +void SharesheetBubbleViewDelegate::ShowBubble( std::vector<::sharesheet::TargetInfo> targets, apps::mojom::IntentPtr intent, ::sharesheet::DeliveredCallback delivered_callback, @@ -40,7 +42,7 @@ std::move(close_callback)); } -void CrosSharesheetServiceDelegate::ShowNearbyShareBubbleForArc( +void SharesheetBubbleViewDelegate::ShowNearbyShareBubbleForArc( apps::mojom::IntentPtr intent, ::sharesheet::DeliveredCallback delivered_callback, ::sharesheet::CloseCallback close_callback) { @@ -59,17 +61,17 @@ std::move(close_callback)); } -void CrosSharesheetServiceDelegate::OnActionLaunched() { +void SharesheetBubbleViewDelegate::OnActionLaunched() { sharesheet_bubble_view_->ShowActionView(); } -void CrosSharesheetServiceDelegate::SetBubbleSize(int width, int height) { +void SharesheetBubbleViewDelegate::SetBubbleSize(int width, int height) { DCHECK_GT(width, 0); DCHECK_GT(height, 0); sharesheet_bubble_view_->ResizeBubble(width, height); } -void CrosSharesheetServiceDelegate::CloseBubble( +void SharesheetBubbleViewDelegate::CloseBubble( ::sharesheet::SharesheetResult result) { views::Widget::ClosedReason reason = views::Widget::ClosedReason::kUnspecified; @@ -83,7 +85,7 @@ sharesheet_bubble_view_->CloseBubble(reason); } -bool CrosSharesheetServiceDelegate::IsBubbleVisible() const { +bool SharesheetBubbleViewDelegate::IsBubbleVisible() const { return sharesheet_bubble_view_->GetWidget() && sharesheet_bubble_view_->GetWidget()->IsVisible(); }
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h new file mode 100644 index 0000000..d3860a2 --- /dev/null +++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h
@@ -0,0 +1,60 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_ASH_SHARESHEET_SHARESHEET_BUBBLE_VIEW_DELEGATE_H_ +#define CHROME_BROWSER_UI_ASH_SHARESHEET_SHARESHEET_BUBBLE_VIEW_DELEGATE_H_ + +#include "chrome/browser/sharesheet/sharesheet_ui_delegate.h" + +namespace sharesheet { +class SharesheetServiceDelegate; +} // namespace sharesheet + +namespace ash { +namespace sharesheet { + +class SharesheetBubbleView; + +// SharesheetBubbleViewDelegate is the SharesheetUiDelegate for +// SharesheetBubbleView. +class SharesheetBubbleViewDelegate : public ::sharesheet::SharesheetUiDelegate { + public: + SharesheetBubbleViewDelegate( + gfx::NativeWindow native_window, + ::sharesheet::SharesheetServiceDelegate* sharesheet_service_delegate); + ~SharesheetBubbleViewDelegate() override = default; + SharesheetBubbleViewDelegate(const SharesheetBubbleViewDelegate&) = delete; + SharesheetBubbleViewDelegate& operator=(const SharesheetBubbleViewDelegate&) = + delete; + + // ::sharesheet::SharesheetUiDelegate: + void ShowBubble(std::vector<::sharesheet::TargetInfo> targets, + apps::mojom::IntentPtr intent, + ::sharesheet::DeliveredCallback delivered_callback, + ::sharesheet::CloseCallback close_callback) override; + +#if BUILDFLAG(IS_CHROMEOS_ASH) + void ShowNearbyShareBubbleForArc( + apps::mojom::IntentPtr intent, + ::sharesheet::DeliveredCallback delivered_callback, + ::sharesheet::CloseCallback close_callback) override; +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + + void OnActionLaunched() override; + + // ::sharesheet::SharesheetController: + void SetBubbleSize(int width, int height) override; + void CloseBubble(::sharesheet::SharesheetResult result) override; + + private: + bool IsBubbleVisible() const; + + // Owned by views. + SharesheetBubbleView* sharesheet_bubble_view_; +}; + +} // namespace sharesheet +} // namespace ash + +#endif // CHROME_BROWSER_UI_ASH_SHARESHEET_SHARESHEET_BUBBLE_VIEW_DELEGATE_H_
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h b/chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h index c03c8bb..03babcd 100644 --- a/chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h +++ b/chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h
@@ -60,6 +60,7 @@ std::vector<std::u16string> ExtractShareText(); const gfx::VectorIcon& GetTextVectorIcon(); + // TODO(crbug.com/1233830): Move business logic out of UI code. void ResolveImages(); void ResolveImage(size_t index); void LoadImage(const base::FilePath& file_path,
diff --git a/chrome/browser/ui/ash/test_wallpaper_controller.cc b/chrome/browser/ui/ash/test_wallpaper_controller.cc index 3645f95..9ee3522 100644 --- a/chrome/browser/ui/ash/test_wallpaper_controller.cc +++ b/chrome/browser/ui/ash/test_wallpaper_controller.cc
@@ -41,7 +41,6 @@ void TestWallpaperController::SetCustomWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const base::FilePath& file_path, ash::WallpaperLayout layout, bool preview_mode, @@ -52,7 +51,6 @@ void TestWallpaperController::SetCustomWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, ash::WallpaperLayout layout, const gfx::ImageSkia& image, @@ -81,7 +79,6 @@ void TestWallpaperController::SetDefaultWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, bool show_wallpaper) { ++set_default_wallpaper_count_; } @@ -94,7 +91,6 @@ void TestWallpaperController::SetPolicyWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& data) { NOTIMPLEMENTED(); } @@ -106,7 +102,6 @@ bool TestWallpaperController::SetThirdPartyWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, ash::WallpaperLayout layout, const gfx::ImageSkia& image) { @@ -150,15 +145,12 @@ ++remove_always_on_top_wallpaper_count_; } -void TestWallpaperController::RemoveUserWallpaper( - const AccountId& account_id, - const std::string& wallpaper_files_id) { +void TestWallpaperController::RemoveUserWallpaper(const AccountId& account_id) { ++remove_user_wallpaper_count_; } void TestWallpaperController::RemovePolicyWallpaper( - const AccountId& account_id, - const std::string& wallpaper_files_id) { + const AccountId& account_id) { NOTIMPLEMENTED(); }
diff --git a/chrome/browser/ui/ash/test_wallpaper_controller.h b/chrome/browser/ui/ash/test_wallpaper_controller.h index 6428e1f8..6bfe331b 100644 --- a/chrome/browser/ui/ash/test_wallpaper_controller.h +++ b/chrome/browser/ui/ash/test_wallpaper_controller.h
@@ -47,13 +47,11 @@ const base::FilePath& custom_wallpapers, const base::FilePath& device_policy_wallpaper) override; void SetCustomWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const base::FilePath& file_path, ash::WallpaperLayout layout, bool preview_mode, SetCustomWallpaperCallback callback) override; void SetCustomWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, ash::WallpaperLayout layout, const gfx::ImageSkia& image, @@ -66,18 +64,15 @@ const std::string& image_data, SetOnlineWallpaperCallback callback) override; void SetDefaultWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, bool show_wallpaper) override; void SetCustomizedDefaultWallpaperPaths( const base::FilePath& customized_default_small_path, const base::FilePath& customized_default_large_path) override; void SetPolicyWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& data) override; void SetDevicePolicyWallpaperPath( const base::FilePath& device_policy_wallpaper_path) override; bool SetThirdPartyWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, ash::WallpaperLayout layout, const gfx::ImageSkia& image) override; @@ -90,10 +85,8 @@ void ShowOneShotWallpaper(const gfx::ImageSkia& image) override; void ShowAlwaysOnTopWallpaper(const base::FilePath& image_path) override; void RemoveAlwaysOnTopWallpaper() override; - void RemoveUserWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id) override; - void RemovePolicyWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id) override; + void RemoveUserWallpaper(const AccountId& account_id) override; + void RemovePolicyWallpaper(const AccountId& account_id) override; void GetOfflineWallpaperList( GetOfflineWallpaperListCallback callback) override; void SetAnimationDuration(base::TimeDelta animation_duration) override;
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc index 6a3bc078..922a49af 100644 --- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc +++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
@@ -115,13 +115,22 @@ chromeos::SystemSaltGetter::Get()->GetRawSalt(); } -// Calls |callback| when system salt is ready. (|CanGetFilesId| returns true.) -void AddCanGetFilesIdCallback(base::OnceClosure callback) { - // System salt may not be initialized in tests. - if (chromeos::SystemSaltGetter::IsInitialized()) { - chromeos::SystemSaltGetter::Get()->AddOnSystemSaltReady( - std::move(callback)); +void GetFilesIdSaltReady( + const AccountId& account_id, + base::OnceCallback<void(const std::string&)> files_id_callback) { + DCHECK(CanGetFilesId()); + std::string stored_value; + if (user_manager::known_user::GetStringPref(account_id, kWallpaperFilesId, + &stored_value)) { + std::move(files_id_callback).Run(stored_value); + return; } + + const std::string wallpaper_files_id = + HashWallpaperFilesIdStr(account_id.GetUserEmail()); + user_manager::known_user::SetStringPref(account_id, kWallpaperFilesId, + wallpaper_files_id); + std::move(files_id_callback).Run(wallpaper_files_id); } // Returns true if |users| contains users other than device local accounts. @@ -282,33 +291,16 @@ return g_wallpaper_controller_client_instance; } -std::string WallpaperControllerClientImpl::GetFilesId( - const AccountId& account_id) const { - DCHECK(CanGetFilesId()); - std::string stored_value; - if (user_manager::known_user::GetStringPref(account_id, kWallpaperFilesId, - &stored_value)) { - return stored_value; - } - - const std::string wallpaper_files_id = - HashWallpaperFilesIdStr(account_id.GetUserEmail()); - user_manager::known_user::SetStringPref(account_id, kWallpaperFilesId, - wallpaper_files_id); - return wallpaper_files_id; -} - void WallpaperControllerClientImpl::SetCustomWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, ash::WallpaperLayout layout, const gfx::ImageSkia& image, bool preview_mode) { if (!IsKnownUser(account_id)) return; - wallpaper_controller_->SetCustomWallpaper( - account_id, wallpaper_files_id, file_name, layout, image, preview_mode); + wallpaper_controller_->SetCustomWallpaper(account_id, file_name, layout, + image, preview_mode); } void WallpaperControllerClientImpl::SetOnlineWallpaper( @@ -352,28 +344,17 @@ if (!data || !IsKnownUser(account_id)) return; - // Postpone setting the wallpaper until we can get files id. See - // https://crbug.com/615239. - if (!CanGetFilesId()) { - AddCanGetFilesIdCallback(base::BindOnce( - &WallpaperControllerClientImpl::SetPolicyWallpaper, - weak_factory_.GetWeakPtr(), account_id, std::move(data))); - return; - } - - wallpaper_controller_->SetPolicyWallpaper(account_id, GetFilesId(account_id), - *data); + wallpaper_controller_->SetPolicyWallpaper(account_id, *data); } bool WallpaperControllerClientImpl::SetThirdPartyWallpaper( const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, ash::WallpaperLayout layout, const gfx::ImageSkia& image) { return IsKnownUser(account_id) && - wallpaper_controller_->SetThirdPartyWallpaper( - account_id, wallpaper_files_id, file_name, layout, image); + wallpaper_controller_->SetThirdPartyWallpaper(account_id, file_name, + layout, image); } void WallpaperControllerClientImpl::ConfirmPreviewWallpaper() { @@ -415,19 +396,7 @@ if (!IsKnownUser(account_id)) return; - // Postpone removing the wallpaper until we can get files id. - if (!CanGetFilesId()) { - LOG(WARNING) - << "Cannot get wallpaper files id in RemoveUserWallpaper. This " - "should never happen under normal circumstances."; - AddCanGetFilesIdCallback( - base::BindOnce(&WallpaperControllerClientImpl::RemoveUserWallpaper, - weak_factory_.GetWeakPtr(), account_id)); - return; - } - - wallpaper_controller_->RemoveUserWallpaper(account_id, - GetFilesId(account_id)); + wallpaper_controller_->RemoveUserWallpaper(account_id); } void WallpaperControllerClientImpl::RemovePolicyWallpaper( @@ -435,19 +404,7 @@ if (!IsKnownUser(account_id)) return; - // Postpone removing the wallpaper until we can get files id. - if (!CanGetFilesId()) { - LOG(WARNING) - << "Cannot get wallpaper files id in RemovePolicyWallpaper. This " - "should never happen under normal circumstances."; - AddCanGetFilesIdCallback( - base::BindOnce(&WallpaperControllerClientImpl::RemovePolicyWallpaper, - weak_factory_.GetWeakPtr(), account_id)); - return; - } - - wallpaper_controller_->RemovePolicyWallpaper(account_id, - GetFilesId(account_id)); + wallpaper_controller_->RemovePolicyWallpaper(account_id); } void WallpaperControllerClientImpl::GetOfflineWallpaperList( @@ -540,6 +497,13 @@ return success; } +void WallpaperControllerClientImpl::GetFilesId( + const AccountId& account_id, + base::OnceCallback<void(const std::string&)> files_id_callback) const { + chromeos::SystemSaltGetter::Get()->AddOnSystemSaltReady(base::BindOnce( + &GetFilesIdSaltReady, account_id, std::move(files_id_callback))); +} + void WallpaperControllerClientImpl::ActiveUserChanged( user_manager::User* active_user) { active_user->AddProfileCreatedObserver(base::BindOnce( @@ -643,19 +607,7 @@ if (!IsKnownUser(account_id)) return; - // Postpone setting the wallpaper until we can get files id. - if (!CanGetFilesId()) { - LOG(WARNING) - << "Cannot get wallpaper files id in SetDefaultWallpaper. This " - "should never happen under normal circumstances."; - AddCanGetFilesIdCallback( - base::BindOnce(&WallpaperControllerClient::SetDefaultWallpaper, - weak_factory_.GetWeakPtr(), account_id, show_wallpaper)); - return; - } - - wallpaper_controller_->SetDefaultWallpaper(account_id, GetFilesId(account_id), - show_wallpaper); + wallpaper_controller_->SetDefaultWallpaper(account_id, show_wallpaper); } void WallpaperControllerClientImpl::MigrateCollectionIdFromChromeApp() {
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.h b/chrome/browser/ui/ash/wallpaper_controller_client_impl.h index 02e8fdd..5d4be10 100644 --- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.h +++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
@@ -50,9 +50,6 @@ static WallpaperControllerClientImpl* Get(); - // Returns files identifier for the |account_id|. - std::string GetFilesId(const AccountId& account_id) const; - // ash::WallpaperControllerClient: void OpenWallpaperPicker() override; void MaybeClosePreviewWallpaper() override; @@ -64,6 +61,9 @@ DailyWallpaperUrlFetchedCallback callback) override; bool SaveWallpaperToDriveFs(const AccountId& account_id, const base::FilePath& origin) override; + void GetFilesId(const AccountId& account_id, + base::OnceCallback<void(const std::string&)> + files_id_callback) const override; // user_manager::UserManager::UserSessionStateObserver: void ActiveUserChanged(user_manager::User* active_user) override; @@ -74,7 +74,6 @@ // Wrappers around the ash::WallpaperController interface. void SetCustomWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, ash::WallpaperLayout layout, const gfx::ImageSkia& image, @@ -95,7 +94,6 @@ void SetPolicyWallpaper(const AccountId& account_id, std::unique_ptr<std::string> data); bool SetThirdPartyWallpaper(const AccountId& account_id, - const std::string& wallpaper_files_id, const std::string& file_name, ash::WallpaperLayout layout, const gfx::ImageSkia& image);
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc index 18ef118..11b5d97 100644 --- a/chrome/browser/ui/tab_helpers.cc +++ b/chrome/browser/ui/tab_helpers.cc
@@ -37,6 +37,7 @@ #include "chrome/browser/media/history/media_history_contents_observer.h" #include "chrome/browser/media/media_engagement_service.h" #include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_observer.h" +#include "chrome/browser/metrics/metrics_services_web_contents_observer.h" #include "chrome/browser/metrics/oom/out_of_memory_reporter.h" #include "chrome/browser/navigation_predictor/navigation_predictor_preconnect_client.h" #include "chrome/browser/net/net_error_tab_helper.h" @@ -281,6 +282,8 @@ MediaEngagementService::CreateWebContentsObserver(web_contents); if (base::FeatureList::IsEnabled(media::kUseMediaHistoryStore)) MediaHistoryContentsObserver::CreateForWebContents(web_contents); + metrics::MetricsServicesWebContentsObserver::CreateForWebContents( + web_contents); MixedContentSettingsTabHelper::CreateForWebContents(web_contents); NavigationMetricsRecorder::CreateForWebContents(web_contents); NavigationPredictorPreconnectClient::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc index 9f4d6e6..5f2f4e9d 100644 --- a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc +++ b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc
@@ -4,11 +4,19 @@ #include "chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h" +#include "base/strings/strcat.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/views/chrome_layout_provider.h" #include "chrome/grit/generated_resources.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/download_request_utils.h" #include "content/public/browser/web_contents.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/metadata/metadata_impl_macros.h" +#include "ui/base/webui/web_ui_util.h" +#include "ui/resources/grit/ui_resources.h" #include "ui/views/border.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/button/md_text_button.h" @@ -45,7 +53,9 @@ views::View* anchor_view, content::WebContents* web_contents, const gfx::Image& image) - : LocationBarBubbleDelegateView(anchor_view, nullptr), image_(image) { + : LocationBarBubbleDelegateView(anchor_view, nullptr), + image_(image), + web_contents_(web_contents) { SetButtons(ui::DIALOG_BUTTON_NONE); SetTitle(IDS_BROWSER_SHARING_SCREENSHOT_POST_CAPTURE_TITLE); } @@ -112,7 +122,7 @@ // Edit button. auto edit_button = std::make_unique<views::MdTextButton>( - base::BindRepeating(&ScreenshotCapturedBubble::DownloadButtonPressed, + base::BindRepeating(&ScreenshotCapturedBubble::EditButtonPressed, base::Unretained(this)), l10n_util::GetStringUTF16( IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_EDIT_BUTTON_LABEL)); @@ -120,7 +130,7 @@ // Share button. auto share_button = std::make_unique<views::MdTextButton>( - base::BindRepeating(&ScreenshotCapturedBubble::DownloadButtonPressed, + base::BindRepeating(&ScreenshotCapturedBubble::ShareButtonPressed, base::Unretained(this)), l10n_util::GetStringUTF16( IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_SHARE_BUTTON_LABEL)); @@ -173,8 +183,57 @@ // End controls row } +/*static*/ +const std::u16string ScreenshotCapturedBubble::GetFilenameForURL( + const GURL& url) { + if (!url.has_host() || url.HostIsIPAddress()) + return u"chrome_screenshot.png"; + + return base::ASCIIToUTF16( + base::StrCat({"chrome_screenshot_", url.host(), ".png"})); +} + void ScreenshotCapturedBubble::DownloadButtonPressed() { - NOTIMPLEMENTED(); + // Returns closest scaling to parameter (1.0). + const gfx::ImageSkia& image_ref = image_view_->GetImage(); + const gfx::ImageSkiaRep& image_rep = image_ref.GetRepresentation(1.0f); + const SkBitmap& bitmap = image_rep.GetBitmap(); + const GURL data_url = GURL(webui::GetBitmapDataUrl(bitmap)); + + Browser* browser = chrome::FindBrowserWithWebContents(web_contents_); + content::DownloadManager* download_manager = + browser->profile()->GetDownloadManager(); + // TODO(crbug.com/1186839): Update the annotation's |setting| and + // |chrome_policy| fields once the Sharing Hub is landed. + net::NetworkTrafficAnnotationTag traffic_annotation = + net::DefineNetworkTrafficAnnotation("desktop_screenshot_save", R"( + semantics { + sender: "Desktop Screenshots" + description: + "The user may capture a selection of the current page. This bubble " + "view has a download button to save the generated image to disk. " + trigger: "User clicks 'download' in a bubble view launched from the " + "omnibox after the 'Screenshot' option is selected in the sharing " + "hub and a selection is made on the page. " + data: "A capture of a portion of the current webpage." + destination: LOCAL + } + policy { + cookies_allowed: NO + setting: + "No user-visible setting for this feature. Experiment and rollout to " + "be coordinated via Chrome Variations." + policy_exception_justification: + "Not implemented, considered not required." + })"); + std::unique_ptr<download::DownloadUrlParameters> params = + content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame( + web_contents_, data_url, traffic_annotation); + // Suggest a name incorporating the hostname. Protocol, TLD, etc are + // not taken into consideration. Duplicate names get automatic suffixes. + params->set_suggested_name( + GetFilenameForURL(web_contents_->GetLastCommittedURL())); + download_manager->DownloadUrl(std::move(params)); } void ScreenshotCapturedBubble::ShareButtonPressed() {
diff --git a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h index 90a9110..f495921 100644 --- a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h +++ b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h
@@ -42,6 +42,8 @@ bool ShouldShowCloseButton() const override; void WindowClosing() override; + static const std::u16string GetFilenameForURL(const GURL& url); + // views::BubbleDialogDelegateView: void Init() override; @@ -53,6 +55,8 @@ const gfx::Image& image_; + content::WebContents* web_contents_; + // Pointers to view widgets; weak. views::ImageView* image_view_ = nullptr; views::MdTextButton* download_button_ = nullptr;
diff --git a/chrome/browser/ui/views/toolbar/reload_button.cc b/chrome/browser/ui/views/toolbar/reload_button.cc index fd2b95048..73d9b015 100644 --- a/chrome/browser/ui/views/toolbar/reload_button.cc +++ b/chrome/browser/ui/views/toolbar/reload_button.cc
@@ -12,6 +12,7 @@ #include "chrome/app/chrome_command_ids.h" #include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/command_updater.h" +#include "chrome/browser/external_protocol/external_protocol_handler.h" #include "chrome/browser/themes/theme_properties.h" #include "chrome/browser/ui/view_ids.h" #include "chrome/grit/generated_resources.h" @@ -161,6 +162,15 @@ } void ReloadButton::ButtonPressed(const ui::Event& event) { + // This is called in order to signal that external protocol dialogs are + // allowed to show due to a user action, which are likely to happen on the + // next page load after the reload button is clicked. + // Ideally, the browser UI's event system would notify ExternalProtocolHandler + // that a user action occurred and we are OK to open the dialog, but for some + // reason that isn't happening every time the reload button is clicked. See + // http://crbug.com/1206456 + ExternalProtocolHandler::PermitLaunchUrl(); + ClearPendingMenu(); if (visible_mode_ == Mode::kStop) {
diff --git a/chrome/browser/ui/views/toolbar/reload_button_browsertest.cc b/chrome/browser/ui/views/toolbar/reload_button_browsertest.cc new file mode 100644 index 0000000..abee95f --- /dev/null +++ b/chrome/browser/ui/views/toolbar/reload_button_browsertest.cc
@@ -0,0 +1,42 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/external_protocol/external_protocol_handler.h" +#include "chrome/browser/ui/view_ids.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/views/toolbar/reload_button.h" +#include "chrome/browser/ui/views/toolbar/toolbar_view.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/interactive_test_utils.h" +#include "content/public/test/browser_test.h" + +class ReloadButtonBrowserTest : public InProcessBrowserTest { + public: + ReloadButtonBrowserTest() = default; + + content::WebContents* GetWebContents() { + return browser()->tab_strip_model()->GetActiveWebContents(); + } +}; + +IN_PROC_BROWSER_TEST_F(ReloadButtonBrowserTest, AllowExternalProtocols) { + const char fake_protocol[] = "fake"; + + // Call LaunchUrl once to trigger the blocked state. + GURL url("fake://example.test"); + ExternalProtocolHandler::LaunchUrl( + url, + base::BindRepeating(&ReloadButtonBrowserTest::GetWebContents, + base::Unretained(this)), + ui::PAGE_TRANSITION_LINK, true, url::Origin::Create(url)); + ASSERT_EQ(ExternalProtocolHandler::BLOCK, + ExternalProtocolHandler::GetBlockState(fake_protocol, nullptr, + browser()->profile())); + + // Clicking the reload button should remove the blocked state. + ui_test_utils::ClickOnView(browser(), VIEW_ID_RELOAD_BUTTON); + ASSERT_NE(ExternalProtocolHandler::BLOCK, + ExternalProtocolHandler::GetBlockState(fake_protocol, nullptr, + browser()->profile())); +}
diff --git a/chrome/browser/ui/web_applications/app_browser_controller.cc b/chrome/browser/ui/web_applications/app_browser_controller.cc index 45bc007..5cc3800 100644 --- a/chrome/browser/ui/web_applications/app_browser_controller.cc +++ b/chrome/browser/ui/web_applications/app_browser_controller.cc
@@ -161,20 +161,20 @@ browser_(browser), theme_provider_( ThemeService::CreateBoundThemeProvider(browser_->profile(), this)), - system_app_type_( - HasAppId() - ? GetSystemWebAppTypeForAppId(browser_->profile(), GetAppId()) - : absl::nullopt), - provider_(system_app_type_ - ? WebAppProvider::GetForSystemWebApps(browser_->profile()) - : WebAppProvider::GetForWebApps(browser_->profile())), + system_app_type_(HasAppId() ? WebAppProvider::Get(browser->profile()) + ->system_web_app_manager() + .GetSystemAppTypeForAppId(GetAppId()) + : absl::nullopt), has_tab_strip_( - (system_app_type_ && - provider_->system_web_app_manager().ShouldHaveTabStrip( - system_app_type_.value())) || + (system_app_type_.has_value() && + WebAppProvider::Get(browser->profile()) + ->system_web_app_manager() + .ShouldHaveTabStrip(system_app_type_.value())) || (base::FeatureList::IsEnabled(features::kDesktopPWAsTabStrip) && HasAppId() && - provider_->registrar().IsTabbedWindowModeEnabled(GetAppId()))) { + WebAppProvider::Get(browser->profile()) + ->registrar() + .IsTabbedWindowModeEnabled(GetAppId()))) { browser->tab_strip_model()->AddObserver(this); } @@ -330,8 +330,9 @@ if (!system_app_type_) return true; - return provider_->system_web_app_manager().ShouldHaveReloadButtonInMinimalUi( - system_app_type_.value()); + return WebAppProvider::Get(browser()->profile()) + ->system_web_app_manager() + .ShouldHaveReloadButtonInMinimalUi(system_app_type_.value()); } std::u16string AppBrowserController::GetLaunchFlashText() const { @@ -367,8 +368,9 @@ gfx::Rect AppBrowserController::GetDefaultBounds() const { if (system_app_type_.has_value()) { - return provider_->system_web_app_manager().GetDefaultBounds( - system_app_type_.value(), browser()); + return WebAppProvider::Get(browser()->profile()) + ->system_web_app_manager() + .GetDefaultBounds(system_app_type_.value(), browser()); } return gfx::Rect(); @@ -464,7 +466,9 @@ return raw_title; std::u16string app_name = - base::UTF8ToUTF16(provider_->registrar().GetAppShortName(GetAppId())); + base::UTF8ToUTF16(WebAppProvider::Get(browser()->profile()) + ->registrar() + .GetAppShortName(GetAppId())); if (base::StartsWith(raw_title, app_name)) { return raw_title; } else if (raw_title.empty()) {
diff --git a/chrome/browser/ui/web_applications/app_browser_controller.h b/chrome/browser/ui/web_applications/app_browser_controller.h index 9d1b129d..4bb72df57 100644 --- a/chrome/browser/ui/web_applications/app_browser_controller.h +++ b/chrome/browser/ui/web_applications/app_browser_controller.h
@@ -36,7 +36,6 @@ namespace web_app { class WebAppBrowserController; -class WebAppProvider; enum class SystemAppType; // Returns true if |app_url| and |page_url| are the same origin. To avoid @@ -245,7 +244,6 @@ absl::optional<SkColor> last_background_color_; absl::optional<SystemAppType> system_app_type_; - WebAppProvider* provider_; const bool has_tab_strip_;
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc index ff0037c..d1ae373 100644 --- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc +++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -778,7 +778,7 @@ if (url.host_piece() == chrome::kChromeUIAddSupervisionHost) return &NewWebUI<chromeos::AddSupervisionUI>; if (url.host_piece() == chrome::kChromeUIAudioHost) - return &NewWebUI<AudioUI>; + return &NewWebUI<chromeos::AudioUI>; if (url.host_piece() == chrome::kChromeUIBluetoothPairingHost) return &NewWebUI<chromeos::BluetoothPairingDialogUI>; // TODO(crbug.com/1147032): The certificates settings page is temporarily
diff --git a/chrome/browser/ui/webui/chromeos/audio/BUILD.gn b/chrome/browser/ui/webui/chromeos/audio/BUILD.gn new file mode 100644 index 0000000..9b051c33 --- /dev/null +++ b/chrome/browser/ui/webui/chromeos/audio/BUILD.gn
@@ -0,0 +1,12 @@ +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//mojo/public/tools/bindings/mojom.gni") + +assert(is_chromeos, "Audio WebUI is Chrome OS only.") + +mojom("mojo_bindings") { + sources = [ "audio.mojom" ] + webui_module_path = "/" +}
diff --git a/chrome/browser/ui/webui/chromeos/audio/OWNERS b/chrome/browser/ui/webui/chromeos/audio/OWNERS index 2ac9a65..f6042c8 100644 --- a/chrome/browser/ui/webui/chromeos/audio/OWNERS +++ b/chrome/browser/ui/webui/chromeos/audio/OWNERS
@@ -1,2 +1,5 @@ yuhsuan@chromium.org hychao@chromium.org + +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio.mojom b/chrome/browser/ui/webui/chromeos/audio/audio.mojom new file mode 100644 index 0000000..b5460eb --- /dev/null +++ b/chrome/browser/ui/webui/chromeos/audio/audio.mojom
@@ -0,0 +1,27 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module audio.mojom; + +// Interface for creating page handler +interface PageHandlerFactory { + // Creates a page handler to update and respond to requests or + // audio device information + CreatePageHandler(pending_remote<Page> page, + pending_receiver<PageHandler> handler); +}; + +// Interface for browser-side handler +// Respond to calls from the WebUI page. +interface PageHandler { + // Request to get audio device information from the browser side + GetAudioDeviceInfo() => (string device_name); +}; + +// Interface for the WebUI page +// Respond to calls from the browser end +interface Page { + // Updates the page with new audio device information + UpdateDeviceInfo(string device_name); +};
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio_handler.cc b/chrome/browser/ui/webui/chromeos/audio/audio_handler.cc new file mode 100644 index 0000000..679126e9 --- /dev/null +++ b/chrome/browser/ui/webui/chromeos/audio/audio_handler.cc
@@ -0,0 +1,25 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <utility> + +#include "chrome/browser/ui/webui/chromeos/audio/audio_handler.h" + +namespace chromeos { + +AudioHandler::AudioHandler( + mojo::PendingReceiver<audio::mojom::PageHandler> receiver, + mojo::PendingRemote<audio::mojom::Page> page) + : page_(std::move(page)), receiver_(this, std::move(receiver)) {} + +AudioHandler::~AudioHandler() = default; + +void AudioHandler::GetAudioDeviceInfo( + audio::mojom::PageHandler::GetAudioDeviceInfoCallback callback) { + const std::string mock_device_name = "mock"; + std::move(callback).Run(mock_device_name); +} + +} // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio_handler.h b/chrome/browser/ui/webui/chromeos/audio/audio_handler.h new file mode 100644 index 0000000..99ce62c --- /dev/null +++ b/chrome/browser/ui/webui/chromeos/audio/audio_handler.h
@@ -0,0 +1,33 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_HANDLER_H_ + +#include "chrome/browser/ui/webui/chromeos/audio/audio.mojom.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" + +namespace chromeos { + +class AudioHandler : public audio::mojom::PageHandler { + public: + AudioHandler(mojo::PendingReceiver<audio::mojom::PageHandler> receiver, + mojo::PendingRemote<audio::mojom::Page> page); + AudioHandler(const AudioHandler&) = delete; + AudioHandler& operator=(const AudioHandler&) = delete; + ~AudioHandler() override; + + void GetAudioDeviceInfo(GetAudioDeviceInfoCallback callback) override; + + private: + mojo::Remote<audio::mojom::Page> page_; + mojo::Receiver<audio::mojom::PageHandler> receiver_; +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_HANDLER_H_
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio_ui.cc b/chrome/browser/ui/webui/chromeos/audio/audio_ui.cc index 2b219d9..df08426 100644 --- a/chrome/browser/ui/webui/chromeos/audio/audio_ui.cc +++ b/chrome/browser/ui/webui/chromeos/audio/audio_ui.cc
@@ -4,6 +4,9 @@ #include "chrome/browser/ui/webui/chromeos/audio/audio_ui.h" +#include <memory> +#include <utility> + #include "chrome/browser/ui/webui/webui_util.h" #include "chrome/common/url_constants.h" #include "chrome/grit/audio_resources.h" @@ -13,7 +16,9 @@ #include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui_data_source.h" -AudioUI::AudioUI(content::WebUI* web_ui) : content::WebUIController(web_ui) { +namespace chromeos { + +AudioUI::AudioUI(content::WebUI* web_ui) : ui::MojoWebUIController(web_ui) { // Set up the chrome://audio source. content::WebUIDataSource* html_source = content::WebUIDataSource::Create(chrome::kChromeUIAudioHost); @@ -27,4 +32,21 @@ content::WebUIDataSource::Add(browser_context, html_source); } +WEB_UI_CONTROLLER_TYPE_IMPL(AudioUI) + AudioUI::~AudioUI() = default; + +void AudioUI::BindInterface( + mojo::PendingReceiver<audio::mojom::PageHandlerFactory> receiver) { + factory_receiver_.reset(); + factory_receiver_.Bind(std::move(receiver)); +} + +void AudioUI::CreatePageHandler( + mojo::PendingRemote<audio::mojom::Page> page, + mojo::PendingReceiver<audio::mojom::PageHandler> receiver) { + page_handler_ = + std::make_unique<AudioHandler>(std::move(receiver), std::move(page)); +} + +} // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio_ui.h b/chrome/browser/ui/webui/chromeos/audio/audio_ui.h index 35583f6..4c9dc82 100644 --- a/chrome/browser/ui/webui/chromeos/audio/audio_ui.h +++ b/chrome/browser/ui/webui/chromeos/audio/audio_ui.h
@@ -5,15 +5,37 @@ #ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_UI_H_ #define CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_UI_H_ -#include "content/public/browser/web_ui_controller.h" +#include "chrome/browser/ui/webui/chromeos/audio/audio.mojom.h" +#include "chrome/browser/ui/webui/chromeos/audio/audio_handler.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "ui/webui/mojo_web_ui_controller.h" -// The WebUI for chrome://audio -class AudioUI : public content::WebUIController { +namespace chromeos { + +// The WebUI Controller for chrome://audio +class AudioUI : public ui::MojoWebUIController, + public audio::mojom::PageHandlerFactory { public: explicit AudioUI(content::WebUI* web_ui); AudioUI(const AudioUI&) = delete; AudioUI& operator=(const AudioUI&) = delete; ~AudioUI() override; + + void BindInterface( + mojo::PendingReceiver<audio::mojom::PageHandlerFactory> receiver); + + private: + // audio::mojom::PageHandlerFactory + void CreatePageHandler( + mojo::PendingRemote<audio::mojom::Page> page, + mojo::PendingReceiver<audio::mojom::PageHandler> receiver) override; + std::unique_ptr<AudioHandler> page_handler_; + mojo::Receiver<audio::mojom::PageHandlerFactory> factory_receiver_{this}; + WEB_UI_CONTROLLER_TYPE_DECL(); }; +} // namespace chromeos + #endif // CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_UI_H_
diff --git a/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc b/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc index 79b9676..c0b3138 100644 --- a/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc +++ b/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc
@@ -4,7 +4,7 @@ #include "base/test/scoped_feature_list.h" #include "chrome/browser/nearby_sharing/common/nearby_share_features.h" -#include "chrome/browser/sharesheet/sharesheet_types.h" +#include "chrome/browser/sharesheet/sharesheet_controller.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.h" @@ -21,7 +21,6 @@ class TestSharesheetController : public sharesheet::SharesheetController { public: // sharesheet::SharesheetController - Profile* GetProfile() override { return nullptr; } void SetBubbleSize(int width, int height) override {} void CloseBubble(::sharesheet::SharesheetResult result) override { // The NearbyShareDialogUI can only invoke this method with a cancellation
diff --git a/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc b/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc index 002c53e..ab2e123 100644 --- a/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc +++ b/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc
@@ -17,6 +17,7 @@ #include "chrome/browser/ui/webui/webui_util.h" #include "chrome/common/webui_url_constants.h" #include "chrome/grit/generated_resources.h" +#include "chromeos/strings/grit/chromeos_strings.h" #include "content/public/browser/web_ui_data_source.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_device.h" @@ -201,6 +202,10 @@ IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_KEYBOARD_MOUSE_COMBO}, {"bluetoothDeviceType_unknown", IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_UNKNOWN}, + {"bluetoothDeviceListCurrentlyConnected", + IDS_BLUETOOTH_DEVICE_LIST_CURRENTLY_CONNECTED}, + {"bluetoothDeviceListPreviouslyConnected", + IDS_BLUETOOTH_DEVICE_LIST_PREVIOUSLY_CONNECTED}, }; html_source->AddLocalizedStrings(kLocalizedStrings); chromeos::bluetooth::AddLoadTimeData(html_source);
diff --git a/chrome/browser/ui/webui/whats_new/whats_new_handler.cc b/chrome/browser/ui/webui/whats_new/whats_new_handler.cc index 978671ad..bcbd21c 100644 --- a/chrome/browser/ui/webui/whats_new/whats_new_handler.cc +++ b/chrome/browser/ui/webui/whats_new/whats_new_handler.cc
@@ -5,6 +5,7 @@ #include "chrome/browser/ui/webui/whats_new/whats_new_handler.h" #include "base/bind.h" +#include "base/metrics/histogram_functions.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile.h" @@ -25,6 +26,28 @@ const int64_t kMaxDownloadBytes = 1024 * 1024; +namespace whats_new { + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class LoadEvent { + kLoadStart = 0, + kLoadSuccess = 1, + kLoadFailAndShowError = 2, + kLoadFailAndFallbackToNtp = 3, + kMaxValue = kLoadFailAndFallbackToNtp, +}; + +} // namespace whats_new + +namespace { + +void LogLoadEvent(whats_new::LoadEvent event) { + base::UmaHistogramEnumeration("WhatsNew.LoadEvent", event); +} + +} // namespace + WhatsNewHandler::WhatsNewHandler() = default; WhatsNewHandler::~WhatsNewHandler() = default; @@ -63,6 +86,7 @@ } void WhatsNewHandler::Fetch(const GURL& url, OnFetchResultCallback on_result) { + LogLoadEvent(whats_new::LoadEvent::kLoadStart); auto traffic_annotation = net::DefineNetworkTrafficAnnotation("whats_new_handler", R"( semantics { @@ -124,12 +148,15 @@ if (!browser) return; + LogLoadEvent(whats_new::LoadEvent::kLoadFailAndFallbackToNtp); content::OpenURLParams params(GURL(chrome::kChromeUINewTabPageURL), content::Referrer(), WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_AUTO_BOOKMARK, false); browser->OpenURL(params); } else { + LogLoadEvent(success ? whats_new::LoadEvent::kLoadSuccess + : whats_new::LoadEvent::kLoadFailAndShowError); // Update pref if successfully shown automatically. if (success && is_auto) { whats_new::SetLastVersion(g_browser_process->local_state());
diff --git a/chrome/browser/usb/web_usb_detector.cc b/chrome/browser/usb/web_usb_detector.cc index 340754c..b35d1d1a 100644 --- a/chrome/browser/usb/web_usb_detector.cc +++ b/chrome/browser/usb/web_usb_detector.cc
@@ -184,6 +184,14 @@ WebUsbDetector::~WebUsbDetector() = default; void WebUsbDetector::Initialize() { +#if defined(OS_WIN) + // The WebUSB device detector is disabled on Windows due to jank and hangs + // caused by enumerating devices. The new USB backend is designed to resolve + // these issues so enable it for testing. https://crbug.com/656702 + if (!base::FeatureList::IsEnabled(device::kNewUsbBackend)) + return; +#endif // defined(OS_WIN) + // Tests may set a fake manager. if (!device_manager_) { // Receive mojo::Remote<UsbDeviceManager> from DeviceService.
diff --git a/chrome/services/file_util/public/cpp/zip_file_creator.cc b/chrome/services/file_util/public/cpp/zip_file_creator.cc index 93461829..6b68aca 100644 --- a/chrome/services/file_util/public/cpp/zip_file_creator.cc +++ b/chrome/services/file_util/public/cpp/zip_file_creator.cc
@@ -87,10 +87,12 @@ remote_zip_file_creator_.set_disconnect_handler( base::BindOnce(&ZipFileCreator::ReportResult, this, kError)); - remote_zip_file_creator_->CreateZipFile( - std::move(directory), src_relative_paths_, std::move(file), - listener_.BindNewPipeAndPassRemote(), - base::BindOnce(&ZipFileCreator::OnFinished, this)); + remote_zip_file_creator_->CreateZipFile(std::move(directory), + src_relative_paths_, std::move(file), + listener_.BindNewPipeAndPassRemote()); + + listener_.set_disconnect_handler( + base::BindOnce(&ZipFileCreator::ReportResult, this, kError)); } void ZipFileCreator::BindDirectory(
diff --git a/chrome/services/file_util/public/cpp/zip_file_creator.h b/chrome/services/file_util/public/cpp/zip_file_creator.h index bf8d039..5e70f89 100644 --- a/chrome/services/file_util/public/cpp/zip_file_creator.h +++ b/chrome/services/file_util/public/cpp/zip_file_creator.h
@@ -67,7 +67,7 @@ mojo::PendingReceiver<filesystem::mojom::Directory> receiver) const; // Called when the ZipFileCreator service finished. - void OnFinished(bool success); + void OnFinished(bool success) override; // Notifies by calling |result_callback| specified in the constructor the end // of the ZIP operation.
diff --git a/chrome/services/file_util/public/mojom/zip_file_creator.mojom b/chrome/services/file_util/public/mojom/zip_file_creator.mojom index e66e6378..c9c393d 100644 --- a/chrome/services/file_util/public/mojom/zip_file_creator.mojom +++ b/chrome/services/file_util/public/mojom/zip_file_creator.mojom
@@ -16,6 +16,9 @@ // Regularly called during ZIP creation operation to report progress, with the // total number of bytes, files and directories processed so far. OnProgress(uint64 bytes, uint32 files, uint32 directories); + + // Called once after the ZIP operation finished. + OnFinished(bool success); }; // Service that zips files and folders. @@ -28,6 +31,5 @@ CreateZipFile(pending_remote<filesystem.mojom.Directory> src_dir, array<mojo_base.mojom.FilePath> relative_paths, mojo_base.mojom.File zip_file, - pending_remote<ZipListener> listener) - => (bool success); + pending_remote<ZipListener> listener); };
diff --git a/chrome/services/file_util/zip_file_creator.cc b/chrome/services/file_util/zip_file_creator.cc index 3a5f4d05..2281eb6 100644 --- a/chrome/services/file_util/zip_file_creator.cc +++ b/chrome/services/file_util/zip_file_creator.cc
@@ -155,42 +155,42 @@ PendingDirectory src_dir, const std::vector<base::FilePath>& relative_paths, base::File zip_file, - PendingListener listener, - CreateZipFileCallback callback) { + PendingListener listener) { DCHECK(zip_file.IsValid()); for (const base::FilePath& path : relative_paths) { if (path.IsAbsolute() || path.ReferencesParent()) { // Paths are expected to be relative. If there are not, the API is used // incorrectly and this is an error. - std::move(callback).Run(/*success=*/false); + Listener(std::move(listener))->OnFinished(/*success=*/false); return; } } - runner_->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&ZipFileCreator::WriteZipFile, this, std::move(src_dir), - std::move(relative_paths), std::move(zip_file), - std::move(listener)), - std::move(callback)); + runner_->PostTask( + FROM_HERE, base::BindOnce(&ZipFileCreator::WriteZipFile, this, + std::move(src_dir), std::move(relative_paths), + std::move(zip_file), std::move(listener))); } -bool ZipFileCreator::WriteZipFile( +void ZipFileCreator::WriteZipFile( PendingDirectory src_dir, const std::vector<base::FilePath>& relative_paths, base::File zip_file, - PendingListener listener) const { + PendingListener pending_listener) const { MojoFileAccessor file_accessor(std::move(src_dir)); - return zip::Zip({ + const Listener listener(std::move(pending_listener)); + const bool success = zip::Zip({ .file_accessor = &file_accessor, .dest_fd = zip_file.GetPlatformFile(), .src_files = relative_paths, - .progress_callback = base::BindRepeating( - &ZipFileCreator::OnProgress, this, Listener(std::move(listener))), + .progress_callback = base::BindRepeating(&ZipFileCreator::OnProgress, + this, std::cref(listener)), .progress_period = base::TimeDelta::FromMilliseconds(1000), .recursive = true, }); + + listener->OnFinished(success); } bool ZipFileCreator::OnProgress(const Listener& listener,
diff --git a/chrome/services/file_util/zip_file_creator.h b/chrome/services/file_util/zip_file_creator.h index 92663fc..c8c1ada 100644 --- a/chrome/services/file_util/zip_file_creator.h +++ b/chrome/services/file_util/zip_file_creator.h
@@ -47,12 +47,11 @@ void CreateZipFile(PendingDirectory src_dir, const std::vector<base::FilePath>& relative_paths, base::File zip_file, - PendingListener listener, - CreateZipFileCallback callback) override; + PendingListener listener) override; // Zips |src_dir| files given by |relative_paths| into |zip_file|. // Must be run in a separate task runner. - bool WriteZipFile(PendingDirectory src_dir, + void WriteZipFile(PendingDirectory src_dir, const std::vector<base::FilePath>& relative_paths, base::File zip_file, PendingListener listener) const;
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 251019c..907532e 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -1787,6 +1787,7 @@ "../browser/ssl/ocsp_browsertest.cc", "../browser/ssl/security_state_tab_helper_browsertest.cc", "../browser/ssl/ssl_browsertest.cc", + "../browser/ssl/ssl_prerender_browsertest.cc", "../browser/ssl/stateful_ssl_host_state_delegate_test.cc", "../browser/storage/durable_storage_browsertest.cc", "../browser/storage_access_api/api_browsertest.cc", @@ -8102,6 +8103,7 @@ "../browser/ui/views/tabs/tab_hover_card_bubble_view_interactive_uitest.cc", "../browser/ui/views/test/view_event_test_base.cc", "../browser/ui/views/test/view_event_test_base.h", + "../browser/ui/views/toolbar/reload_button_browsertest.cc", "../browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc", "../browser/ui/views/translate/translate_bubble_test_utils_views.cc", "../browser/ui/views/translate/translate_bubble_view_interactive_uitest.cc", @@ -9448,6 +9450,10 @@ "//chrome/test/chromedriver", "//testing:test_scripts_shared", ] - data = [ "//testing/scripts/run_variations_smoke_tests.py" ] + data = [ + "//testing/scripts/run_variations_smoke_tests.py", + "//testing/scripts/variations_smoke_test_data/", + "//third_party/webdriver/pylib/", + ] } }
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeTabUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeTabUtils.java index c9d07cb..0266f38 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeTabUtils.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeTabUtils.java
@@ -747,15 +747,17 @@ final CallbackHelper createdCallback = new CallbackHelper(); final TabModel tabModel = testRule.getActivity().getTabModelSelector().getModel(expectIncognito); - tabModel.addObserver(new TabModelObserver() { - @Override - public void didAddTab( - Tab tab, @TabLaunchType int type, @TabCreationState int creationState) { - if (TextUtils.equals(expectedUrl, tab.getUrl().getSpec())) { - createdCallback.notifyCalled(); - tabModel.removeObserver(this); + TestThreadUtils.runOnUiThreadBlocking(() -> { + tabModel.addObserver(new TabModelObserver() { + @Override + public void didAddTab( + Tab tab, @TabLaunchType int type, @TabCreationState int creationState) { + if (TextUtils.equals(expectedUrl, tab.getUrl().getSpec())) { + createdCallback.notifyCalled(); + tabModel.removeObserver(this); + } } - } + }); }); TestTouchUtils.performLongClickOnMainSync( @@ -797,15 +799,17 @@ final CallbackHelper createdCallback = new CallbackHelper(); final TabModel tabModel = backgroundActivity.getTabModelSelector().getModel(expectIncognito); - tabModel.addObserver(new TabModelObserver() { - @Override - public void didAddTab( - Tab tab, @TabLaunchType int type, @TabCreationState int creationState) { - if (TextUtils.equals(expectedUrl, tab.getUrl().getSpec())) { - createdCallback.notifyCalled(); - tabModel.removeObserver(this); + TestThreadUtils.runOnUiThreadBlocking(() -> { + tabModel.addObserver(new TabModelObserver() { + @Override + public void didAddTab( + Tab tab, @TabLaunchType int type, @TabCreationState int creationState) { + if (TextUtils.equals(expectedUrl, tab.getUrl().getSpec())) { + createdCallback.notifyCalled(); + tabModel.removeObserver(this); + } } - } + }); }); TestTouchUtils.performLongClickOnMainSync(
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/SadTabRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/SadTabRule.java index ea87140..49b1f3dc8 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/SadTabRule.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/SadTabRule.java
@@ -36,26 +36,28 @@ assert mTab != null; if (mSadTab == null) { - mSadTab = new SadTab(mTab) { - private boolean mShowing; + TestThreadUtils.runOnUiThreadBlocking(() -> { + mSadTab = new SadTab(mTab) { + private boolean mShowing; - @Override - public void show( - Context context, Runnable suggestionAction, Runnable buttonAction) { - mShowing = true; - } + @Override + public void show( + Context context, Runnable suggestionAction, Runnable buttonAction) { + mShowing = true; + } - @Override - public void removeIfPresent() { - mShowing = false; - } + @Override + public void removeIfPresent() { + mShowing = false; + } - @Override - public boolean isShowing() { - return mShowing; - } - }; - TestThreadUtils.runOnUiThreadBlocking(() -> SadTab.initForTesting(mTab, mSadTab)); + @Override + public boolean isShowing() { + return mShowing; + } + }; + SadTab.initForTesting(mTab, mSadTab); + }); } TestThreadUtils.runOnUiThreadBlocking(() -> { if (show) {
diff --git a/chrome/test/base/perf/performance_test.cc b/chrome/test/base/perf/performance_test.cc index 8d402fe..43bd8293 100644 --- a/chrome/test/base/perf/performance_test.cc +++ b/chrome/test/base/perf/performance_test.cc
@@ -69,9 +69,8 @@ base::RunLoop run_loop; TestWallpaperObserver observer(run_loop.QuitClosure()); WallpaperControllerClientImpl::Get()->SetCustomWallpaper( - user_manager::StubAccountId(), /*wallpaper_files_id=*/"dummyid", - /*file_name=*/"dummyfilename", ash::WALLPAPER_LAYOUT_CENTER_CROPPED, - image, /*preview_mode=*/false); + user_manager::StubAccountId(), /*file_name=*/"dummyfilename", + ash::WALLPAPER_LAYOUT_CENTER_CROPPED, image, /*preview_mode=*/false); run_loop.Run(); } #endif // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/test/data/service_worker/blank.html b/chrome/test/data/service_worker/blank.html new file mode 100644 index 0000000..ed8e56e --- /dev/null +++ b/chrome/test/data/service_worker/blank.html
@@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> + <head> + <title>An empty page</title> + </head> + <body> + </body> +</html>
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn index 9e972ec..bb71d61 100644 --- a/chrome/test/data/webui/BUILD.gn +++ b/chrome/test/data/webui/BUILD.gn
@@ -297,6 +297,7 @@ "$root_gen_dir/chrome/test/data/webui/nearby_share/shared/nearby_onboarding_page_test.m.js", "$root_gen_dir/chrome/test/data/webui/nearby_share/shared/nearby_page_template_test.m.js", "$root_gen_dir/chrome/test/data/webui/nearby_share/shared/nearby_visibility_page_test.m.js", + "$root_gen_dir/chrome/test/data/webui/settings/chromeos/fake_bluetooth_config.m.js", "$root_gen_dir/chrome/test/data/webui/settings/chromeos/add_users_tests.m.js", "$root_gen_dir/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.m.js", "$root_gen_dir/chrome/test/data/webui/settings/chromeos/ambient_mode_photos_page_test.m.js", @@ -376,6 +377,9 @@ "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_about_page_tests.m.js", "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_bluetooth_page_tests.m.js", "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_bluetooth_summary_tests.m.js", + "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_bluetooth_devices_subpage_tests.m.js", + "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.m.js", + "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_item_tests.m.js", "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_edit_dictionary_page_test.m.js", "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_files_page_test.m.js", "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_languages_page_v2_tests.m.js",
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn b/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn index 57bb59ff..b42ab68 100644 --- a/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn +++ b/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn
@@ -18,12 +18,14 @@ ":accelerator_view_test", ":fake_shortcut_provider_test", ":shortcut_customization_test", + ":shortcut_customization_test_util", ":shortcut_customization_unified_test", ] } js_library("accelerator_edit_dialog_test") { deps = [ + ":shortcut_customization_test_util", "../..:chai_assert", "//ash/webui/shortcut_customization_ui/resources:accelerator_edit_dialog", ] @@ -32,6 +34,7 @@ js_library("accelerator_edit_view_test") { deps = [ + ":shortcut_customization_test_util", "../..:chai_assert", "//ash/webui/shortcut_customization_ui/resources:accelerator_edit_view", ] @@ -40,6 +43,7 @@ js_library("accelerator_row_test") { deps = [ + ":shortcut_customization_test_util", "../..:chai_assert", "//ash/webui/shortcut_customization_ui/resources:accelerator_row", ] @@ -56,6 +60,7 @@ js_library("accelerator_view_test") { deps = [ + ":shortcut_customization_test_util", "../..:chai_assert", "//ash/webui/shortcut_customization_ui/resources:accelerator_view", ] @@ -82,5 +87,9 @@ externs_list = [ "$externs_path/mocha-2.5.js" ] } +js_library("shortcut_customization_test_util") { + deps = [] +} + js_library("shortcut_customization_unified_test") { }
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_dialog_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_dialog_test.js index 6a1f11e..a1c6094 100644 --- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_dialog_test.js +++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_dialog_test.js
@@ -8,6 +8,8 @@ import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js'; +import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js'; + export function acceleratorEditDialogTest() { /** @type {?AcceleratorEditDialogElement} */ let viewElement = null; @@ -25,26 +27,16 @@ test('LoadsBasicDialog', async () => { /** @type {!AcceleratorInfo} */ - const acceleratorInfo1 = { - accelerator: /** @type {!AcceleratorKeys} */ ({ - modifiers: Modifier.CONTROL | Modifier.SHIFT, - key: 71, - key_display: 'g', - }), - type: AcceleratorType.kDefault, - state: AcceleratorState.kEnabled, - }; + const acceleratorInfo1 = CreateDefaultAccelerator( + Modifier.CONTROL | Modifier.SHIFT, + /*key=*/ 71, + /*key_display=*/ 'g'); /** @type {!AcceleratorInfo} */ - const acceleratorInfo2 = { - accelerator: /** @type {!AcceleratorKeys} */ ({ - modifiers: Modifier.CONTROL, - key: 67, - key_display: 'c', - }), - type: AcceleratorType.kDefault, - state: AcceleratorState.kEnabled, - }; + const acceleratorInfo2 = CreateDefaultAccelerator( + Modifier.CONTROL, + /*key=*/ 67, + /*key_display=*/ 'c'); const accelerators = [acceleratorInfo1, acceleratorInfo2]; @@ -91,26 +83,16 @@ test('AddShortcut', async () => { /** @type {!AcceleratorInfo} */ - const acceleratorInfo1 = { - accelerator: /** @type {!AcceleratorKeys} */ ({ - modifiers: Modifier.CONTROL | Modifier.SHIFT, - key: 71, - key_display: 'g', - }), - type: AcceleratorType.kDefault, - state: AcceleratorState.kEnabled, - }; + const acceleratorInfo1 = CreateDefaultAccelerator( + Modifier.CONTROL | Modifier.SHIFT, + /*key=*/ 71, + /*key_display=*/ 'g'); /** @type {!AcceleratorInfo} */ - const acceleratorInfo2 = { - accelerator: /** @type {!AcceleratorKeys} */ ({ - modifiers: Modifier.CONTROL, - key: 67, - key_display: 'c', - }), - type: AcceleratorType.kDefault, - state: AcceleratorState.kEnabled, - }; + const acceleratorInfo2 = CreateDefaultAccelerator( + Modifier.CONTROL | Modifier.SHIFT, + /*key=*/ 67, + /*key_display=*/ 'c'); const acceleratorInfos = [acceleratorInfo1, acceleratorInfo2]; const description = 'test shortcut';
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_view_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_view_test.js index 7314777..bab9b91 100644 --- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_view_test.js +++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_view_test.js
@@ -8,6 +8,8 @@ import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js'; +import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js'; + export function acceleratorEditViewTest() { /** @type {?AcceleratorEditViewElement} */ let editViewElement = null; @@ -25,15 +27,10 @@ test('LoadsBasicEditView', async () => { /** @type {!AcceleratorInfo} */ - const acceleratorInfo = { - accelerator: /** @type {!AcceleratorKeys} */ ({ - modifiers: Modifier.CONTROL | Modifier.SHIFT, - key: 71, - key_display: 'g', - }), - type: AcceleratorType.kDefault, - state: AcceleratorState.kEnabled, - }; + const acceleratorInfo = CreateDefaultAccelerator( + Modifier.CONTROL | Modifier.SHIFT, + /*key=*/ 71, + /*key_display=*/ 'g'); editViewElement.acceleratorInfo = acceleratorInfo; await flush();
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js index 69105dda..19e8433 100644 --- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js +++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js
@@ -8,6 +8,8 @@ import {assertEquals} from '../../chai_assert.js'; +import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js'; + export function acceleratorRowTest() { /** @type {?AcceleratorRowElement} */ let rowElement = null; @@ -24,27 +26,16 @@ }); test('LoadsBasicRow', async () => { - /** @type {!AcceleratorInfo} */ - const acceleratorInfo1 = { - accelerator: /** @type {!AcceleratorKeys} */ ({ - modifiers: Modifier.CONTROL | Modifier.SHIFT, - key: 71, - key_display: 'g', - }), - type: AcceleratorType.kDefault, - state: AcceleratorState.kEnabled, - }; + const acceleratorInfo1 = CreateDefaultAccelerator( + Modifier.CONTROL | Modifier.SHIFT, + /*key=*/ 71, + /*key_display=*/ 'g'); /** @type {!AcceleratorInfo} */ - const acceleratorInfo2 = { - accelerator: /** @type {!AcceleratorKeys} */ ({ - modifiers: Modifier.CONTROL, - key: 67, - key_display: 'c', - }), - type: AcceleratorType.kDefault, - state: AcceleratorState.kEnabled, - }; + const acceleratorInfo2 = CreateDefaultAccelerator( + Modifier.CONTROL, + /*key=*/ 67, + /*key_display=*/ 'c'); const accelerators = [acceleratorInfo1, acceleratorInfo2]; const description = 'test shortcut';
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.js index 58a6a77..36f03e54 100644 --- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.js +++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.js
@@ -6,9 +6,10 @@ import {AcceleratorSubsectionElement} from 'chrome://shortcut-customization/accelerator_subsection.js'; import {AcceleratorInfo, AcceleratorKeys, AcceleratorState, AcceleratorType, Modifier} from 'chrome://shortcut-customization/shortcut_types.js'; - import {assertEquals} from '../../chai_assert.js'; +import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js'; + export function acceleratorSubsectionTest() { /** @type {?AcceleratorSubsectionElement} */ let sectionElement = null; @@ -27,29 +28,17 @@ // TODO(jimmyxgong): Update this test after retrieving accelerators is // implemented for a subsection. test('LoadsBasicSection', async () => { - // TODO(jimmyxgong): Update the type of the test accelerator with the mojom - // version. /** @type {!AcceleratorInfo} */ - const acceleratorInfo1 = { - accelerator: /** @type {!AcceleratorKeys} */ ({ - modifiers: Modifier.CONTROL | Modifier.SHIFT, - key: 71, - key_display: 'g', - }), - type: AcceleratorType.kDefault, - state: AcceleratorState.kEnabled, - }; + const acceleratorInfo1 = CreateDefaultAccelerator( + Modifier.CONTROL | Modifier.SHIFT, + /*key=*/ 71, + /*key_display=*/ 'g'); /** @type {!AcceleratorInfo} */ - const acceleratorInfo2 = { - accelerator: /** @type {!AcceleratorKeys} */ ({ - modifiers: Modifier.CONTROL, - key: 67, - key_display: 'c', - }), - type: AcceleratorType.kDefault, - state: AcceleratorState.kEnabled, - }; + const acceleratorInfo2 = CreateDefaultAccelerator( + Modifier.CONTROL | Modifier.SHIFT, + /*key=*/ 67, + /*key_display=*/ 'c'); const accelerators = [acceleratorInfo1, acceleratorInfo2]; const description = 'test shortcut';
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.js index 16034e26..5b7ee4d 100644 --- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.js +++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.js
@@ -8,6 +8,8 @@ import {assertEquals, assertTrue} from '../../chai_assert.js'; +import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js'; + export function acceleratorViewTest() { /** @type {?AcceleratorViewElement} */ let viewElement = null; @@ -51,15 +53,10 @@ test('EditableAccelerator', async () => { /** @type {!AcceleratorInfo} */ - const acceleratorInfo = { - accelerator: /** @type {!AcceleratorKeys} */ ({ - modifiers: Modifier.CONTROL | Modifier.SHIFT, - key: 71, - key_display: 'g', - }), - type: AcceleratorType.kDefault, - state: AcceleratorState.kEnabled, - }; + const acceleratorInfo = CreateDefaultAccelerator( + Modifier.CONTROL | Modifier.SHIFT, + /*key=*/ 71, + /*key_display=*/ 'g'); viewElement.acceleratorInfo = acceleratorInfo; await flush();
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test_util.js b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test_util.js new file mode 100644 index 0000000..501da9b --- /dev/null +++ b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test_util.js
@@ -0,0 +1,23 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {AcceleratorInfo, AcceleratorKeys, AcceleratorState, AcceleratorType} from 'chrome://shortcut-customization/shortcut_types.js'; + +/** + * @param {number} modifier + * @param {number} keycode + * @param {string} key_display + * @return {!AcceleratorInfo} + */ +export function CreateDefaultAccelerator(modifier, keycode, key_display) { + return /** @type {!AcceleratorInfo} */ ({ + accelerator: /** @type {!AcceleratorKeys} */ ({ + modifiers: modifier, + key: keycode, + key_display: key_display, + }), + type: AcceleratorType.kDefault, + state: AcceleratorState.kEnabled, + }); +} \ No newline at end of file
diff --git a/chrome/test/data/webui/settings/BUILD.gn b/chrome/test/data/webui/settings/BUILD.gn index e3cec78..c658077 100644 --- a/chrome/test/data/webui/settings/BUILD.gn +++ b/chrome/test/data/webui/settings/BUILD.gn
@@ -102,7 +102,7 @@ ":safety_check_page_test", #":search_engines_page_test", - ":search_page_test", + #":search_page_test", #":search_settings_test", #":secure_dns_interactive_test", @@ -157,7 +157,7 @@ #":test_profile_info_browser_proxy", #":test_reset_browser_proxy", - ":test_search_engines_browser_proxy", + #":test_search_engines_browser_proxy", ":test_site_settings_prefs_browser_proxy", ":test_sync_browser_proxy", ":test_util",
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn index 3b1e59b..9e1ad9e 100644 --- a/chrome/test/data/webui/settings/chromeos/BUILD.gn +++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -76,6 +76,7 @@ "esim_rename_dialog_test.js", "fake_bluetooth.js", "fake_bluetooth_private.js", + "fake_bluetooth_config.js", "fake_quick_unlock_private.js", "fake_quick_unlock_uma.js", "fake_receive_manager.js", @@ -122,10 +123,13 @@ "os_about_page_tests.js", "os_bluetooth_page_tests.js", "os_bluetooth_summary_tests.js", + "os_bluetooth_devices_subpage_tests.js", "os_edit_dictionary_page_test.js", "os_files_page_test.js", "os_languages_page_v2_tests.js", "os_reset_page_test.js", + "os_paired_bluetooth_list_tests.js", + "os_paired_bluetooth_list_item_tests.js", "os_people_page_test.js", "os_printing_page_tests.js", "os_privacy_page_test.js",
diff --git a/chrome/test/data/webui/settings/chromeos/fake_bluetooth_config.js b/chrome/test/data/webui/settings/chromeos/fake_bluetooth_config.js new file mode 100644 index 0000000..2a50dcf --- /dev/null +++ b/chrome/test/data/webui/settings/chromeos/fake_bluetooth_config.js
@@ -0,0 +1,106 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO(crbug.com/1010321): Use cros_bluetooth_config.mojom-webui.js instead +// as non-module JS is deprecated. +import 'chrome://resources/mojo/chromeos/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-lite.js'; + +/** + * @fileoverview Fake implementation of CrosBluetoothConfig for testing. + */ + +/** @implements {chromeos.bluetoothConfig.mojom.CrosBluetoothConfigInterface} */ +export class FakeBluetoothConfig { + constructor() { + /** @private {!chromeos.bluetoothConfig.mojom.BluetoothSystemProperties} */ + this.systemProperties_ = { + systemState: + chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled, + }; + + /** + * @private {!Array< + * !chromeos.bluetoothConfig.mojom.SystemPropertiesObserver>} + */ + this.observers_ = []; + } + + /** + * @override + * @param {SystemPropertiesObserver} observer + */ + observeSystemProperties(observer) { + this.observers_.push(observer); + this.notifyObserversPropertiesUpdated_(); + } + + /** + * @override + * Begins the operation to enable/disable Bluetooth. If the systemState is + * current disabled, transitions to enabling. If the systemState is + * currently enabled, transitions to disabled. Does nothing if already in the + * requested state. This method should be followed by a call to + * completeSetBluetoothEnabledStateForTest() to complete the operation. + * @param {boolean} enabled + */ + setBluetoothEnabledState(enabled) { + const bluetoothSystemState = + chromeos.bluetoothConfig.mojom.BluetoothSystemState; + const systemState = this.systemProperties_.systemState; + if ((enabled && systemState === bluetoothSystemState.kEnabled) || + (!enabled && systemState === bluetoothSystemState.kDisabled)) { + return; + } + + this.setSystemState( + enabled ? bluetoothSystemState.kEnabling : + bluetoothSystemState.kDisabling); + } + + /** + * @param {chromeos.bluetoothConfig.mojom.BluetoothSystemState} systemState + */ + setSystemState(systemState) { + const newSystemProperties = {...this.systemProperties_}; + newSystemProperties.systemState = systemState; + this.systemProperties_ = newSystemProperties; + this.notifyObserversPropertiesUpdated_(); + } + + /** + * Completes the Bluetooth enable/disable operation. This method should be + * called after setBluetoothEnabledState(). If the systemState is already + * enabled or disabled, does nothing. + * @param {boolean} success Whether the operation should succeed or not. + */ + completeSetBluetoothEnabledState(success) { + const bluetoothSystemState = + chromeos.bluetoothConfig.mojom.BluetoothSystemState; + const systemState = this.systemProperties_.systemState; + if (systemState === bluetoothSystemState.kDisabled || + systemState === bluetoothSystemState.kEnabled) { + return; + } + + if (success) { + this.setSystemState( + systemState === bluetoothSystemState.kDisabling ? + bluetoothSystemState.kDisabled : + bluetoothSystemState.kEnabled); + } else { + this.setSystemState( + systemState === bluetoothSystemState.kDisabling ? + bluetoothSystemState.kEnabled : + bluetoothSystemState.kDisabled); + } + } + + /** + * @private + * Notifies the observer list that systemProperties_ has changed. + */ + notifyObserversPropertiesUpdated_() { + this.observers_.forEach(o => o.onPropertiesUpdated(this.systemProperties_)); + } +} \ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_bluetooth_devices_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/os_bluetooth_devices_subpage_tests.js new file mode 100644 index 0000000..0cc758a0 --- /dev/null +++ b/chrome/test/data/webui/settings/chromeos/os_bluetooth_devices_subpage_tests.js
@@ -0,0 +1,28 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// clang-format off +// #import 'chrome://os-settings/chromeos/os_settings.js'; + +// #import 'chrome://os-settings/strings.m.js'; + +// #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +// #import {assertTrue} from '../../../chai_assert.js'; +// clang-format on + +suite('OsBluetoothDevicesSubpageTest', function() { + /** @type {!SettingsBluetoothDevicesSubpageElement|undefined} */ + let bluetoothDevicesSubpage; + + setup(function() { + bluetoothDevicesSubpage = + document.createElement('os-settings-bluetooth-devices-subpage'); + document.body.appendChild(bluetoothDevicesSubpage); + Polymer.dom.flush(); + }); + + test('Base Test', function() { + assertTrue(!!bluetoothDevicesSubpage); + }); +}); \ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_bluetooth_page_tests.js b/chrome/test/data/webui/settings/chromeos/os_bluetooth_page_tests.js index 94dba62..539c7862 100644 --- a/chrome/test/data/webui/settings/chromeos/os_bluetooth_page_tests.js +++ b/chrome/test/data/webui/settings/chromeos/os_bluetooth_page_tests.js
@@ -9,20 +9,20 @@ // #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; // #import {assertTrue} from '../../../chai_assert.js'; +// #import {FakeBluetoothConfig} from './fake_bluetooth_config.m.js'; // #import {setBluetoothConfigForTesting} from 'chrome://resources/cr_components/chromeos/bluetooth/cros_bluetooth_config.js'; // clang-format on suite('OsBluetoothPageTest', function() { + /** @type {!FakeBluetoothConfig} */ + let bluetoothConfig; + /** @type {!SettingsBluetoothPageElement|undefined} */ let bluetoothPage; setup(function() { - // TODO(crbug.com/1010321): Replace this with fake_cros_bluetooth_config - // when it is created. - setBluetoothConfigForTesting({ - observeSystemProperties: (observer) => {}, - setBluetoothEnabledState: (enabled) => {} - }); + bluetoothConfig = new FakeBluetoothConfig(); + setBluetoothConfigForTesting(bluetoothConfig); bluetoothPage = document.createElement('os-settings-bluetooth-page'); document.body.appendChild(bluetoothPage); Polymer.dom.flush();
diff --git a/chrome/test/data/webui/settings/chromeos/os_bluetooth_summary_tests.js b/chrome/test/data/webui/settings/chromeos/os_bluetooth_summary_tests.js index 7b274eb..0e4c63d 100644 --- a/chrome/test/data/webui/settings/chromeos/os_bluetooth_summary_tests.js +++ b/chrome/test/data/webui/settings/chromeos/os_bluetooth_summary_tests.js
@@ -10,20 +10,40 @@ // #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; // #import {Router, Route, routes} from 'chrome://os-settings/chromeos/os_settings.js'; // #import {assertTrue} from '../../../chai_assert.js'; +// #import {FakeBluetoothConfig} from './fake_bluetooth_config.m.js'; // #import {setBluetoothConfigForTesting} from 'chrome://resources/cr_components/chromeos/bluetooth/cros_bluetooth_config.js'; // clang-format on suite('OsBluetoothSummaryTest', function() { + /** @type {!FakeBluetoothConfig} */ + let bluetoothConfig; + /** @type {!SettingsBluetoothSummaryElement|undefined} */ let bluetoothSummary; + /** + * @type {!chromeos.bluetoothConfig.mojom.SystemPropertiesObserverInterface} + */ + let propertiesObserver; + setup(function() { - // TODO(crbug.com/1010321): Replace this with fake_cros_bluetooth_config - // when it is created. - setBluetoothConfigForTesting({setBluetoothEnabledState: (enabled) => {}}); + bluetoothConfig = new FakeBluetoothConfig(); + setBluetoothConfigForTesting(bluetoothConfig); bluetoothSummary = document.createElement('os-settings-bluetooth-summary'); document.body.appendChild(bluetoothSummary); Polymer.dom.flush(); + + propertiesObserver = { + /** + * SystemPropertiesObserverInterface override + * @param {!chromeos.bluetoothConfig.mojom.BluetoothSystemProperties} + * properties + */ + onPropertiesUpdated(properties) { + bluetoothSummary.systemProperties = properties; + } + }; + bluetoothConfig.observeSystemProperties(propertiesObserver); }); function flushAsync() { @@ -43,24 +63,42 @@ }); test('Toggle button states', async function() { - // TODO(crbug.com/1010321): Remove |mockSystemProperties| once - // fake_cros_bluetooth_config has been added. - const mockSystemProperties = { - systemState: chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled, - }; - bluetoothSummary.systemProperties = {...mockSystemProperties}; + const enableBluetoothToggle = bluetoothSummary.$$('#enableBluetoothToggle'); + assertTrue(!!enableBluetoothToggle); + assertFalse(enableBluetoothToggle.checked); - const enableBluettonToggle = bluetoothSummary.$$('#enableBluetoothToggle'); - assertTrue(!!enableBluettonToggle); - assertTrue(enableBluettonToggle.checked); - - // Simulate disabled state. - mockSystemProperties.systemState = - chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled; - bluetoothSummary.systemProperties = {...mockSystemProperties}; + // Simulate clicking toggle. + enableBluetoothToggle.click(); await flushAsync(); - assertFalse(enableBluettonToggle.checked); - }); + // Toggle should be on since systemState is enabling. + assertTrue(enableBluetoothToggle.checked); + // Mock operation failing. + bluetoothConfig.completeSetBluetoothEnabledState(/*success=*/ false); + await flushAsync(); + + // Toggle should be off again. + assertFalse(enableBluetoothToggle.checked); + + // Click again. + enableBluetoothToggle.click(); + await flushAsync(); + + // Toggle should be on since systemState is enabling. + assertTrue(enableBluetoothToggle.checked); + + // Mock operation success. + bluetoothConfig.completeSetBluetoothEnabledState(/*success=*/ true); + await flushAsync(); + + // Toggle should still be on. + assertTrue(enableBluetoothToggle.checked); + + // Mock systemState becoming unavailable. + bluetoothConfig.setSystemState( + chromeos.bluetoothConfig.mojom.BluetoothSystemState.kUnavailable); + await flushAsync(); + assertTrue(enableBluetoothToggle.disabled); + }); }); \ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_item_tests.js b/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_item_tests.js new file mode 100644 index 0000000..8e24ab187 --- /dev/null +++ b/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_item_tests.js
@@ -0,0 +1,28 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// clang-format off +// #import 'chrome://os-settings/chromeos/os_settings.js'; + +// #import 'chrome://os-settings/strings.m.js'; + +// #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +// #import {assertTrue} from '../../../chai_assert.js'; +// clang-format on + +suite('OsPairedBluetoothListItemTest', function() { + /** @type {!SettingsPairedBluetoothListItemElement|undefined} */ + let pairedBluetoothListItem; + + setup(function() { + pairedBluetoothListItem = + document.createElement('os-settings-paired-bluetooth-list-item'); + document.body.appendChild(pairedBluetoothListItem); + Polymer.dom.flush(); + }); + + test('Base Test', function() { + assertTrue(!!pairedBluetoothListItem); + }); +}); \ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.js b/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.js new file mode 100644 index 0000000..21023043 --- /dev/null +++ b/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.js
@@ -0,0 +1,29 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// clang-format off +// #import 'chrome://os-settings/chromeos/os_settings.js'; + +// #import 'chrome://os-settings/strings.m.js'; + +// #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +// #import {assertTrue} from '../../../chai_assert.js'; +// clang-format on + +suite('OsPairedBluetoothListTest', function() { + /** @type {!SettingsPairedBluetoothListElement|undefined} */ + let pairedBluetoothList; + + setup(function() { + pairedBluetoothList = + document.createElement('os-settings-paired-bluetooth-list'); + document.body.appendChild(pairedBluetoothList); + Polymer.dom.flush(); + }); + + test('Base Test', function() { + const list = pairedBluetoothList.shadowRoot.querySelector('iron-list'); + assertTrue(!!list); + }); +}); \ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js index 649b081..43b56d22 100644 --- a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js +++ b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
@@ -372,10 +372,13 @@ ['NetworkProxySection', 'network_proxy_section_test.m.js'], ['NetworkSummary', 'network_summary_test.m.js'], ['NetworkSummaryItem', 'network_summary_item_test.m.js'], + ['OsBluetoothDevicesSubpage', 'os_bluetooth_devices_subpage_tests.m.js'], ['OsBluetoothPage', 'os_bluetooth_page_tests.m.js'], ['OsBluetoothSummary', 'os_bluetooth_summary_tests.m.js'], ['OsEditDictionaryPage', 'os_edit_dictionary_page_test.m.js'], ['OsLanguagesPageV2', 'os_languages_page_v2_tests.m.js'], + ['OsPairedBluetoothList', 'os_paired_bluetooth_list_tests.m.js'], + ['OsPairedBluetoothListItem', 'os_paired_bluetooth_list_item_tests.m.js'], ['OsSettingsUi', 'os_settings_ui_test.m.js'], ['OsSettingsUi2', 'os_settings_ui_test_2.m.js'], ['OsSettingsMain', 'os_settings_main_test.m.js'],
diff --git a/chrome/test/data/webui/settings/extension_controlled_indicator_tests.js b/chrome/test/data/webui/settings/extension_controlled_indicator_tests.js index 695da6e..da4af4eb 100644 --- a/chrome/test/data/webui/settings/extension_controlled_indicator_tests.js +++ b/chrome/test/data/webui/settings/extension_controlled_indicator_tests.js
@@ -19,7 +19,7 @@ setup(function() { PolymerTest.clearBody(); browserProxy = new TestExtensionControlBrowserProxy(); - ExtensionControlBrowserProxyImpl.instance_ = browserProxy; + ExtensionControlBrowserProxyImpl.setInstance(browserProxy); indicator = document.createElement('extension-controlled-indicator'); indicator.extensionId = 'peiafolljookckjknpgofpbjobgbmpge'; indicator.extensionCanBeDisabled = true;
diff --git a/chrome/test/data/webui/settings/search_engines_page_test.js b/chrome/test/data/webui/settings/search_engines_page_test.js index 66924310..1ca2c52 100644 --- a/chrome/test/data/webui/settings/search_engines_page_test.js +++ b/chrome/test/data/webui/settings/search_engines_page_test.js
@@ -70,7 +70,7 @@ setup(function() { browserProxy = new TestSearchEnginesBrowserProxy(); - SearchEnginesBrowserProxyImpl.instance_ = browserProxy; + SearchEnginesBrowserProxyImpl.setInstance(browserProxy); PolymerTest.clearBody(); dialog = document.createElement('settings-search-engine-dialog'); document.body.appendChild(dialog); @@ -177,7 +177,7 @@ setup(function() { browserProxy = new TestSearchEnginesBrowserProxy(); - SearchEnginesBrowserProxyImpl.instance_ = browserProxy; + SearchEnginesBrowserProxyImpl.setInstance(browserProxy); PolymerTest.clearBody(); entry = document.createElement('settings-search-engine-entry'); entry.set('engine', searchEngine); @@ -311,7 +311,7 @@ others: searchEnginesInfo.others.slice(), extensions: searchEnginesInfo.extensions.slice(), }); - SearchEnginesBrowserProxyImpl.instance_ = browserProxy; + SearchEnginesBrowserProxyImpl.setInstance(browserProxy); PolymerTest.clearBody(); page = document.createElement('settings-search-engines-page'); document.body.appendChild(page); @@ -485,7 +485,7 @@ setup(function() { browserProxy = new TestExtensionControlBrowserProxy(); - ExtensionControlBrowserProxyImpl.instance_ = browserProxy; + ExtensionControlBrowserProxyImpl.setInstance(browserProxy); PolymerTest.clearBody(); entry = document.createElement('settings-omnibox-extension-entry'); entry.set('engine', createSampleOmniboxExtension());
diff --git a/chrome/test/data/webui/settings/search_page_test.js b/chrome/test/data/webui/settings/search_page_test.js index 17f3559..a2b0f9e 100644 --- a/chrome/test/data/webui/settings/search_page_test.js +++ b/chrome/test/data/webui/settings/search_page_test.js
@@ -40,7 +40,7 @@ setup(function() { browserProxy = new TestSearchEnginesBrowserProxy(); browserProxy.setSearchEnginesInfo(generateSearchEngineInfo()); - SearchEnginesBrowserProxyImpl.instance_ = browserProxy; + SearchEnginesBrowserProxyImpl.setInstance(browserProxy); document.body.innerHTML = ''; page = /** @type {!SettingsSearchPageElement} */ ( document.createElement('settings-search-page'));
diff --git a/chrome/test/data/webui/settings/test_search_engines_browser_proxy.js b/chrome/test/data/webui/settings/test_search_engines_browser_proxy.js index 4a004d0..c8407ee 100644 --- a/chrome/test/data/webui/settings/test_search_engines_browser_proxy.js +++ b/chrome/test/data/webui/settings/test_search_engines_browser_proxy.js
@@ -3,8 +3,6 @@ // found in the LICENSE file. // clang-format off -import { SearchEngine,SearchEnginesBrowserProxy, SearchEnginesInfo} from 'chrome://settings/settings.js'; - import {TestBrowserProxy} from '../test_browser_proxy.m.js'; // clang-format on
diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc index 9e5c6cc..b8399fc 100644 --- a/chrome/updater/test/integration_test_commands_system.cc +++ b/chrome/updater/test/integration_test_commands_system.cc
@@ -165,6 +165,9 @@ EXPECT_TRUE(base::PathExists(path)); path = MakeAbsoluteFilePath(path); path = path.Append(FILE_PATH_LITERAL("updater_integration_tests_helper")); +#if defined(OS_WIN) + path = path.AddExtension(L"exe"); +#endif EXPECT_TRUE(base::PathExists(path)); base::CommandLine helper_command(path);
diff --git a/chrome/updater/test/integration_tests_helper.cc b/chrome/updater/test/integration_tests_helper.cc index ae52f63..e63de6a0 100644 --- a/chrome/updater/test/integration_tests_helper.cc +++ b/chrome/updater/test/integration_tests_helper.cc
@@ -25,6 +25,10 @@ #include "chrome/updater/updater_scope.h" #include "url/gurl.h" +#if defined(OS_WIN) +#include "base/win/scoped_com_initializer.h" +#endif + namespace updater { namespace test { namespace { @@ -220,6 +224,11 @@ /*enable_timestamp=*/true, /*enable_tickcount=*/false); +#if defined(OS_WIN) + auto scoped_com_initializer = + std::make_unique<base::win::ScopedCOMInitializer>( + base::win::ScopedCOMInitializer::kMTA); +#endif return MakeAppTestHelper()->Run(); }
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc index 19b879a..78b4ea206 100644 --- a/chrome/updater/test/integration_tests_win.cc +++ b/chrome/updater/test/integration_tests_win.cc
@@ -57,10 +57,14 @@ return test_executable.DirName().AppendASCII(TEST_APP_FULLNAME_STRING ".exe"); } -absl::optional<base::FilePath> GetProductPath() { +absl::optional<base::FilePath> GetProductPath(UpdaterScope scope) { base::FilePath app_data_dir; - if (!base::PathService::Get(base::DIR_LOCAL_APP_DATA, &app_data_dir)) + if (!base::PathService::Get(scope == UpdaterScope::kSystem + ? base::DIR_PROGRAM_FILES + : base::DIR_LOCAL_APP_DATA, + &app_data_dir)) { return absl::nullopt; + } return app_data_dir.AppendASCII(COMPANY_SHORTNAME_STRING) .AppendASCII(PRODUCT_FULLNAME_STRING) .AppendASCII(kUpdaterVersion); @@ -84,7 +88,7 @@ } // namespace absl::optional<base::FilePath> GetInstalledExecutablePath(UpdaterScope scope) { - absl::optional<base::FilePath> path = GetProductPath(); + absl::optional<base::FilePath> path = GetProductPath(scope); if (!path) return absl::nullopt; return path->AppendASCII("updater.exe"); @@ -93,7 +97,7 @@ absl::optional<base::FilePath> GetFakeUpdaterInstallFolderPath( UpdaterScope scope, const base::Version& version) { - absl::optional<base::FilePath> path = GetProductPath(); + absl::optional<base::FilePath> path = GetProductPath(scope); if (!path) return absl::nullopt; return path->AppendASCII(version.GetString()); @@ -162,7 +166,7 @@ } // TODO(crbug.com/1062288): Delete the Wake task. - absl::optional<base::FilePath> path = GetProductPath(); + absl::optional<base::FilePath> path = GetProductPath(scope); EXPECT_TRUE(path); if (path) EXPECT_TRUE(base::DeletePathRecursively(*path)); @@ -230,7 +234,7 @@ // TODO(crbug.com/1062288): Assert there are no Wake tasks. // Files must not exist on the file system. - absl::optional<base::FilePath> path = GetProductPath(); + absl::optional<base::FilePath> path = GetProductPath(scope); EXPECT_TRUE(path); if (path) EXPECT_FALSE(base::PathExists(*path)); @@ -257,7 +261,7 @@ // TODO(crbug.com/1062288): Assert there are Wake tasks. // Files must exist on the file system. - absl::optional<base::FilePath> path = GetProductPath(); + absl::optional<base::FilePath> path = GetProductPath(scope); EXPECT_TRUE(path); if (path) EXPECT_TRUE(base::PathExists(*path)); @@ -268,7 +272,7 @@ // TODO(crbug.com/1062288): Assert there are no Wake tasks. // Files must not exist on the file system. - absl::optional<base::FilePath> path = GetProductPath(); + absl::optional<base::FilePath> path = GetProductPath(scope); EXPECT_TRUE(path); if (path) EXPECT_FALSE(base::PathExists(*path)); @@ -278,7 +282,7 @@ // TODO(crbug.com/1062288): Assert that COM interfaces point to this version. // Files must exist on the file system. - absl::optional<base::FilePath> path = GetProductPath(); + absl::optional<base::FilePath> path = GetProductPath(scope); EXPECT_TRUE(path); if (path) EXPECT_TRUE(base::PathExists(*path)); @@ -323,8 +327,8 @@ SleepFor(5); } -void SetActive(UpdaterScope scope, const std::string& id) { - // TODO(crbug/1159498): Standardize registry access. +void SetActive(UpdaterScope /*scope*/, const std::string& id) { + // TODO(crbug.com/1159498): Standardize registry access. base::win::RegKey key; ASSERT_EQ(key.Create(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(), KEY_WRITE | KEY_WOW64_32KEY), @@ -332,8 +336,8 @@ EXPECT_EQ(key.WriteValue(kDidRun, L"1"), ERROR_SUCCESS); } -void ExpectActive(UpdaterScope scope, const std::string& id) { - // TODO(crbug/1159498): Standardize registry access. +void ExpectActive(UpdaterScope /*scope*/, const std::string& id) { + // TODO(crbug.com/1159498): Standardize registry access. base::win::RegKey key; ASSERT_EQ(key.Open(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(), KEY_READ | KEY_WOW64_32KEY), @@ -343,8 +347,8 @@ EXPECT_EQ(value, L"1"); } -void ExpectNotActive(UpdaterScope scope, const std::string& id) { - // TODO(crbug/1159498): Standardize registry access. +void ExpectNotActive(UpdaterScope /*scope*/, const std::string& id) { + // TODO(crbug.com/1159498): Standardize registry access. base::win::RegKey key; if (key.Open(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(), KEY_READ | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd index 649a8c2..1ea330c 100644 --- a/chromeos/chromeos_strings.grd +++ b/chromeos/chromeos_strings.grd
@@ -1656,6 +1656,14 @@ <message name="IDS_TRAFFIC_COUNTERS_TRAFFIC_COUNTERS" desc="Label for showing traffic counters"> Traffic Counters </message> + + <!-- Bluetooth --> + <message name="IDS_BLUETOOTH_DEVICE_LIST_CURRENTLY_CONNECTED" desc="Title for list displaying paired Bluetooth devices that are currently connected."> + Currently connected + </message> + <message name="IDS_BLUETOOTH_DEVICE_LIST_PREVIOUSLY_CONNECTED" desc="Title for list displaying paired Bluetooth devices that have previously been connected."> + Previously connected + </message> </messages> </release> </grit>
diff --git a/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_CURRENTLY_CONNECTED.png.sha1 b/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_CURRENTLY_CONNECTED.png.sha1 new file mode 100644 index 0000000..bc80186d --- /dev/null +++ b/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_CURRENTLY_CONNECTED.png.sha1
@@ -0,0 +1 @@ +863d6717575560355cb8fdf02928e0beb45132a2 \ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_PREVIOUSLY_CONNECTED.png.sha1 b/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_PREVIOUSLY_CONNECTED.png.sha1 new file mode 100644 index 0000000..bc80186d --- /dev/null +++ b/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_PREVIOUSLY_CONNECTED.png.sha1
@@ -0,0 +1 @@ +863d6717575560355cb8fdf02928e0beb45132a2 \ No newline at end of file
diff --git a/chromeos/components/camera_app_ui/resources.h b/chromeos/components/camera_app_ui/resources.h index 4261ffb..c370eece 100644 --- a/chromeos/components/camera_app_ui/resources.h +++ b/chromeos/components/camera_app_ui/resources.h
@@ -92,6 +92,7 @@ {"new_control_navigation", IDS_NEW_CONTROL_NAVIGATION}, {"new_control_toast", IDS_NEW_CONTROL_TOAST}, {"new_document_scan_toast", IDS_NEW_DOCUMENT_SCAN_TOAST}, + {"no_document_guide_msg", IDS_NO_DOCUMENT_GUIDE_MSG}, {"open_ptz_panel_button", IDS_OPEN_PTZ_PANEL_BUTTON}, {"pan_left_button", IDS_PAN_LEFT_BUTTON}, {"pan_right_button", IDS_PAN_RIGHT_BUTTON},
diff --git a/chromeos/components/camera_app_ui/resources/.eslintrc.js b/chromeos/components/camera_app_ui/resources/.eslintrc.js index 39e91f2..98f4117 100644 --- a/chromeos/components/camera_app_ui/resources/.eslintrc.js +++ b/chromeos/components/camera_app_ui/resources/.eslintrc.js
@@ -381,6 +381,7 @@ 'BarcodeDetector': 'readable', 'FileSystemFileHandle': 'readable', 'FileSystemDirectoryHandle': 'readable', + 'IdleDetector': 'readable', // TODO(b/172879638): Remove this once we have // https://github.com/sindresorhus/globals/pull/171 merged in ESLint and
diff --git a/chromeos/components/camera_app_ui/resources/css/scanner.css b/chromeos/components/camera_app_ui/resources/css/scanner.css index 1b4d90c..932a796 100644 --- a/chromeos/components/camera_app_ui/resources/css/scanner.css +++ b/chromeos/components/camera_app_ui/resources/css/scanner.css
@@ -296,7 +296,7 @@ top: 0; } -#preview-document-corner-overlay.clear div { +#preview-document-corner-overlay:not(.show-corner-indicator) :is(.line, .corner) { display: none; } @@ -342,3 +342,19 @@ height: 1px; width: 1px; } + +#preview-document-corner-overlay .no-document-toast { + background: var(--grey-900); + border-radius: 6px; + color: var(--grey-500); + display: inline-block; + font: normal 400 14px/20px Roboto; + left: 50%; + padding: 10px 8px; + position: absolute; + text-align: center; + top: 22px; + transform: translateX(-50%); + transition: visibility var(--fast1-duration) ease-in; + white-space: nowrap; +}
diff --git a/chromeos/components/camera_app_ui/resources/js/externs/chrome.js b/chromeos/components/camera_app_ui/resources/js/externs/chrome.js index ffe3313..60a2051c 100644 --- a/chromeos/components/camera_app_ui/resources/js/externs/chrome.js +++ b/chromeos/components/camera_app_ui/resources/js/externs/chrome.js
@@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +/* eslint-disable no-unused-vars, no-var */ + /** * It is available since we enabled and filled data in it while creating * content::WebUIDataSource for CCA. @@ -20,7 +22,7 @@ * start: function(): !Promise<void>, * }} */ -window.IdleDetector; +let IdleDetector; /** * @typedef {{
diff --git a/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts b/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts index 61ee7f8..4637960 100644 --- a/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts +++ b/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts
@@ -5,6 +5,9 @@ // TODO(pihsun): Remove this once we fully specify all the types. /* eslint-disable @typescript-eslint/no-explicit-any */ +// ESLint doesn't like "declare class" without jsdoc. +/* eslint-disable require-jsdoc */ + // TODO(b/172340451): Use the types generated by Mojo TypeScript binding // generator once https://crbug.com/1002798 is finished. type MojomNamespace = { @@ -170,3 +173,11 @@ }; export const reportError: (info: ErrorInfo, callback: () => void) => void; } + +// Idle Detection API: This is currently a Chrome only API gated behind Origin +// Trial, and the spec is still in working draft stage. +// https://wicg.github.io/idle-detection/ +declare class IdleDetector extends EventTarget { + screenState: 'locked'|'unlocked'; + start: () => Promise<void>; +}
diff --git a/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js b/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js index cc3c4d5..b35cf6c 100644 --- a/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js +++ b/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js
@@ -146,6 +146,12 @@ StylePropertyMap.prototype.get = function(property) {}; /** + * @param {string} property + * @return {boolean} + */ +StylePropertyMap.prototype.has = function(property) {}; + +/** * @constructor * @extends {CSSStyleValue} * @param {!CSSStyleValue} x
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera.js b/chromeos/components/camera_app_ui/resources/js/views/camera.js index fac45ccf..1a5e08e 100644 --- a/chromeos/components/camera_app_ui/resources/js/views/camera.js +++ b/chromeos/components/camera_app_ui/resources/js/views/camera.js
@@ -351,7 +351,7 @@ this.initOpenPTZPanel_(); // Monitor the states to stop camera when locked/minimized. - const idleDetector = new window.IdleDetector(); + const idleDetector = new IdleDetector(); idleDetector.addEventListener('change', () => { this.locked_ = idleDetector.screenState === 'locked'; if (this.locked_) { @@ -779,6 +779,13 @@ /** * @override */ + waitPreviewReady() { + return this.preview_.waitReadyForTakePhoto(); + } + + /** + * @override + */ async handleResultVideo({resolution, duration, videoSaver, everPaused}) { metrics.sendCaptureEvent({ facing: this.facingMode_,
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js b/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js index e7ce0d2e..d5ba910 100644 --- a/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js +++ b/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js
@@ -131,6 +131,11 @@ } /** + * Timeout to show toast message when no document is detected within the time. + */ +const SHOW_NO_DOCUMENT_TOAST_TIMEOUT_MS = 4000; + +/** * An overlay to show document corner rectangles over preview. */ export class DocumentCornerOverlay { @@ -145,6 +150,13 @@ this.overlay_ = dom.get('#preview-document-corner-overlay', HTMLDivElement); /** + * @const {!HTMLDivElement} + * @private + */ + this.noDocumentToast_ = + dom.getFrom(this.overlay_, '.no-document-toast', HTMLDivElement); + + /** * @type {?string} * @private */ @@ -183,7 +195,13 @@ return corners; })(); - this.clear_(); + /** + * @type {?number} + * @private + */ + this.noDocumentTimerId_ = null; + + this.hide_(); } /** @@ -226,14 +244,17 @@ this.observerId_ = await deviceOperator.registerDocumentCornersObserver( assertString(this.deviceId_), (corners) => { if (corners.length === 0) { - this.clear_(); + this.onNoCornerDetected_(); return; } const rect = this.overlay_.getBoundingClientRect(); const toOverlaySpace = (pt) => new Point(rect.width * pt.x, rect.height * pt.y); - this.setCorners_(corners.map(toOverlaySpace)); + this.onCornerDetected_(corners.map(toOverlaySpace)); }); + this.hide_(); + this.clearNoDocumentTimer_(); + this.setNoDocumentTimer_(); } /** @@ -246,11 +267,12 @@ const nonNullObserverId = this.observerId_; const deviceOperator = assertInstanceof(await DeviceOperator.getInstance(), DeviceOperator); - const isSuccess = deviceOperator.unregisterDocumentCornersObserver( + const isSuccess = await deviceOperator.unregisterDocumentCornersObserver( assertString(this.deviceId_), nonNullObserverId); assert(isSuccess); this.observerId_ = null; - this.clear_(); + this.hide_(); + this.clearNoDocumentTimer_(); } /** @@ -261,13 +283,29 @@ } /** + * @private + */ + onNoCornerDetected_() { + this.hideIndicators_(); + if (this.isNoDocumentToastShown_()) { + return; + } + if (this.noDocumentTimerId_ === null) { + this.setNoDocumentTimer_(); + } + } + + /** * @param {!Array<!Point>} corners */ - setCorners_(corners) { - if (this.overlay_.classList.contains('clear')) { - this.settleCorners_(corners); - } else { + onCornerDetected_(corners) { + this.hideNoDocumentToast_(); + this.clearNoDocumentTimer_(); + if (this.isIndicatorsShown_()) { this.updateCorners_(corners); + } else { + this.showIndicators_(); + this.settleCorners_(corners); } } @@ -277,8 +315,6 @@ * @private */ settleCorners_(corners) { - this.overlay_.classList.remove('clear'); - /** * Start point(corner coordinates + outer shift) of settle animation. * @param {!Point} corn @@ -366,10 +402,78 @@ } /** - * Clears all indicator on overlay. + * Hides overlay related UIs. * @private */ - clear_() { - this.overlay_.classList.add('clear'); + hide_() { + this.hideIndicators_(); + this.hideNoDocumentToast_(); + } + + /** + * @private + * @return {boolean} + */ + isIndicatorsShown_() { + return this.overlay_.classList.contains('show-corner-indicator'); + } + + /** + * @private + */ + showIndicators_() { + this.overlay_.classList.add('show-corner-indicator'); + } + + /** + * @private + */ + hideIndicators_() { + this.overlay_.classList.remove('show-corner-indicator'); + } + + /** + * @private + */ + showNoDocumentToast_() { + this.noDocumentToast_.attributeStyleMap.delete('visibility'); + } + + /** + * @private + */ + hideNoDocumentToast_() { + this.noDocumentToast_.attributeStyleMap.set('visibility', 'hidden'); + } + + /** + * @private + * @return {boolean} + */ + isNoDocumentToastShown_() { + return !this.noDocumentToast_.attributeStyleMap.has('visibility'); + } + + /** + * @private + */ + setNoDocumentTimer_() { + if (this.noDocumentTimerId_ !== null) { + clearTimeout(this.noDocumentTimerId_); + } + this.noDocumentTimerId_ = setTimeout(() => { + this.showNoDocumentToast_(); + this.clearNoDocumentTimer_(); + }, SHOW_NO_DOCUMENT_TOAST_TIMEOUT_MS); + } + + /** + * @private + */ + clearNoDocumentTimer_() { + if (this.noDocumentTimerId_ !== null) { + clearTimeout(this.noDocumentTimerId_); + this.noDocumentTimerId_ = null; + } } }
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/photo.js b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/photo.js index 6593687..4f2419e 100644 --- a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/photo.js +++ b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/photo.js
@@ -53,6 +53,12 @@ * Plays UI effect when taking photo. */ playShutterEffect() {} + + /** + * @return {!Promise} + * @abstract + */ + waitPreviewReady() {} } /** @@ -167,6 +173,7 @@ imageHeight: caps.imageHeight.max, }); } + await this.handler_.waitPreviewReady(); const results = await this.crosImageCapture_.takePhoto(photoSettings); return results[0]; }
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/scanner.js b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/scanner.js index 1a5339d..01a8c868 100644 --- a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/scanner.js +++ b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/scanner.js
@@ -61,6 +61,12 @@ * @abstract */ handleResultDocument(result, name) {} + + /** + * @return {!Promise} + * @abstract + */ + waitPreviewReady() {} } /** @@ -107,6 +113,13 @@ playShutterEffect() { this.handler_.playBlockingShutterEffect(); } + + /** + * @override + */ + waitPreviewReady() { + return this.handler_.waitPreviewReady(); + } } /**
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/square.js b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/square.js index 9e09f7b..ab5c76e 100644 --- a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/square.js +++ b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/square.js
@@ -56,9 +56,9 @@ /** * @override */ - async handleResultPhoto(result, ...args) { + async handleResultPhoto(result, name) { result.blob = await cropSquare(result.blob); - await this.handler_.handleResultPhoto(result, ...args); + await this.handler_.handleResultPhoto(result, name); } /** @@ -67,6 +67,13 @@ playShutterEffect() { this.handler_.playShutterEffect(); } + + /** + * @override + */ + waitPreviewReady() { + return this.handler_.waitPreviewReady(); + } } /**
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/options.js b/chromeos/components/camera_app_ui/resources/js/views/camera/options.js index 36f80ec..b191028 100644 --- a/chromeos/components/camera_app_ui/resources/js/views/camera/options.js +++ b/chromeos/components/camera_app_ui/resources/js/views/camera/options.js
@@ -82,12 +82,10 @@ */ this.audioTrack_ = null; - [['#switch-device', () => this.switchDevice(undefined)], - ['#open-settings', () => nav.open(ViewName.SETTINGS)], - ] - .forEach( - ([selector, fn]) => dom.get(selector, HTMLButtonElement) - .addEventListener('click', fn)); + dom.get('#switch-device', HTMLButtonElement) + .addEventListener('click', () => this.switchDevice(undefined)); + dom.get('#open-settings', HTMLButtonElement) + .addEventListener('click', () => nav.open(ViewName.SETTINGS)); this.toggleMic_.addEventListener('click', () => this.updateAudioByMic_()); this.toggleMirror_.addEventListener('click', () => this.saveMirroring_());
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js b/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js index f4c792d..151b0f5f 100644 --- a/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js +++ b/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js
@@ -14,12 +14,14 @@ import * as nav from '../../nav.js'; import * as state from '../../state.js'; import { + CanceledError, ErrorLevel, ErrorType, Facing, Resolution, } from '../../type.js'; import * as util from '../../util.js'; +import {WaitableEvent} from '../../waitable_event.js'; import {windowController} from '../../window_controller.js'; /** @@ -97,6 +99,12 @@ */ this.constraints_ = null; + /** + * @type {?function()} + * @private + */ + this.cancelWaitReadyForTakePhoto_ = null; + window.addEventListener('resize', () => this.onWindowStatusChanged_()); windowController.addListener(() => this.onWindowStatusChanged_()); @@ -300,12 +308,50 @@ if (deviceOperator !== null) { deviceOperator.dropConnection(deviceId); } + if (this.cancelWaitReadyForTakePhoto_ !== null) { + this.cancelWaitReadyForTakePhoto_(); + } this.stream_ = null; } state.set(state.State.STREAMING, false); } /** + * Waits for preview stream ready for taking photo. + * @return {!Promise} + */ + async waitReadyForTakePhoto() { + if (this.stream_ === null) { + throw new CanceledError('Preview is closed'); + } + + // Chrome use muted state on video track representing no frame input + // returned from preview video for a while and call |takePhoto()| with + // video track in muted state will fail with |kInvalidStateError| exception. + // To mitigate chance of hitting this error, here we ensure frame inputs + // from the preview and checked video muted state before taking photo. + const track = this.getVideoTrack_(); + const waitFrame = async () => { + const onReady = new WaitableEvent(); + const callbackId = this.video_.requestVideoFrameCallback((now) => { + onReady.signal(true); + }); + this.cancelWaitReadyForTakePhoto_ = () => { + this.video_.cancelVideoFrameCallback(callbackId); + onReady.signal(false); + }; + const ready = await onReady.wait(); + this.cancelWaitReadyForTakePhoto_ = null; + return ready; + }; + do { + if (!await waitFrame()) { + throw new CanceledError('Preview is closed'); + } + } while (track.muted); + } + + /** * Checks preview whether to show preview metadata or not. * @private */ @@ -393,6 +439,7 @@ 'ANDROID_CONTROL_AE_ANTIBANDING_MODE_'); const tag = cros.mojom.CameraMetadataTag; + /** @type {!Object<string, function(!Array<number>): void>} */ const metadataEntryHandlers = { [tag.ANDROID_LENS_FOCUS_DISTANCE]: ([value]) => { if (value === 0) {
diff --git a/chromeos/components/camera_app_ui/resources/strings/camera_strings.grd b/chromeos/components/camera_app_ui/resources/strings/camera_strings.grd index 323bae8..b212693 100644 --- a/chromeos/components/camera_app_ui/resources/strings/camera_strings.grd +++ b/chromeos/components/camera_app_ui/resources/strings/camera_strings.grd
@@ -505,6 +505,9 @@ <message desc="Text on ack button in dialog introducing document mode." name="IDS_DOCUMENT_MODE_DIALOG_GOT_IT"> Got it </message> + <message desc="Toast message for guiding user to reposition document when no document in the camera FOV." name="IDS_NO_DOCUMENT_GUIDE_MSG"> + Place all edges of the document within the frame + </message> </messages> </release> </grit>
diff --git a/chromeos/components/camera_app_ui/resources/strings/camera_strings_grd/IDS_NO_DOCUMENT_GUIDE_MSG.png.sha1 b/chromeos/components/camera_app_ui/resources/strings/camera_strings_grd/IDS_NO_DOCUMENT_GUIDE_MSG.png.sha1 new file mode 100644 index 0000000..076d9594 --- /dev/null +++ b/chromeos/components/camera_app_ui/resources/strings/camera_strings_grd/IDS_NO_DOCUMENT_GUIDE_MSG.png.sha1
@@ -0,0 +1 @@ +e9baf1de896a15acff7047007d9c8b4687064912 \ No newline at end of file
diff --git a/chromeos/components/camera_app_ui/resources/views/main.html b/chromeos/components/camera_app_ui/resources/views/main.html index 6c69453ef..11d1153 100644 --- a/chromeos/components/camera_app_ui/resources/views/main.html +++ b/chromeos/components/camera_app_ui/resources/views/main.html
@@ -98,6 +98,8 @@ tabindex="-1" hidden></object> </div> <div id="preview-document-corner-overlay"> + <div class="no-document-toast" tabindex="0" aria-live="polite" + i18n-text="no_document_guide_msg"></div> </div> <div class="barcode-scan-box centered-overlay"></div> <div class="snackbar" aria-live="polite"></div>
diff --git a/chromeos/components/drivefs/OWNERS b/chromeos/components/drivefs/OWNERS index b9fd788..f30cc14 100644 --- a/chromeos/components/drivefs/OWNERS +++ b/chromeos/components/drivefs/OWNERS
@@ -1,6 +1,6 @@ -dats@chromium.org -sammc@chromium.org -slangley@chromium.org +austinct@chromium.org +petermarshall@chromium.org +simmonsjosh@google.com per-file *_mojom_traits*.*=set noparent per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/chromeos/services/ime/BUILD.gn b/chromeos/services/ime/BUILD.gn index 71a81dc..f3cf421 100644 --- a/chromeos/services/ime/BUILD.gn +++ b/chromeos/services/ime/BUILD.gn
@@ -26,7 +26,6 @@ deps = [ ":constants", "//base", - "//chromeos/services/ime/public/cpp:buildflags", "//chromeos/services/ime/public/cpp/shared_lib:interfaces", ] } @@ -49,7 +48,6 @@ ":decoder", "//ash/constants", "//base", - "//chromeos/services/ime/public/cpp:buildflags", "//chromeos/services/ime/public/cpp:rulebased", "//chromeos/services/ime/public/cpp/shared_lib:interfaces", "//chromeos/services/ime/public/mojom", @@ -67,7 +65,6 @@ ":constants", ":decoder", "//base", - "//chromeos/services/ime/public/cpp:buildflags", "//sandbox/linux:sandbox_services", "//sandbox/policy", ]
diff --git a/chromeos/services/ime/constants.cc b/chromeos/services/ime/constants.cc index e8c3a58..1573d5b 100644 --- a/chromeos/services/ime/constants.cc +++ b/chromeos/services/ime/constants.cc
@@ -23,8 +23,6 @@ FILE_PATH_LITERAL("/usr/share/chromeos-assets/input_methods/input_tools"); const base::FilePath::CharType kUserInputMethodsDirPath[] = FILE_PATH_LITERAL("/home/chronos/user/" IME_DIR_STRING); -const base::FilePath::CharType kSharedInputMethodsDirPath[] = - FILE_PATH_LITERAL("/home/chronos/" IME_DIR_STRING); const base::FilePath::CharType kLanguageDataDirName[] = FILE_PATH_LITERAL("google"); #else @@ -34,8 +32,6 @@ FILE_PATH_LITERAL("/tmp/" IME_DIR_STRING); const base::FilePath::CharType kUserInputMethodsDirPath[] = FILE_PATH_LITERAL("/tmp/" IME_DIR_STRING); -const base::FilePath::CharType kSharedInputMethodsDirPath[] = - FILE_PATH_LITERAL("/tmp/" IME_DIR_STRING); const base::FilePath::CharType kLanguageDataDirName[] = FILE_PATH_LITERAL("data"); #endif
diff --git a/chromeos/services/ime/constants.h b/chromeos/services/ime/constants.h index 2d70d88b5..cf03f64 100644 --- a/chromeos/services/ime/constants.h +++ b/chromeos/services/ime/constants.h
@@ -21,17 +21,6 @@ COMPONENT_EXPORT(CHROMEOS_IME_CONSTANTS) extern const base::FilePath::CharType kUserInputMethodsDirPath[]; -// The path of downloaded IME language dictionaries which shared by all users. -// It aims to reduce storage and improve responsiveness by reusing static -// dictionary for all users. This feature is disabled by default. When -// `CrosImeSharedDataEnabled` is on and the IME attempt to load some language -// dictionary which is missing on the device, the IME service will try to -// download it to the shared path. Because the language dictionary will be -// accessible by all users, it will prevent duplicate downloads of the same -// language dictionary. -COMPONENT_EXPORT(CHROMEOS_IME_CONSTANTS) -extern const base::FilePath::CharType kSharedInputMethodsDirPath[]; - // The name of the directory inside the profile where IME data are stored in. COMPONENT_EXPORT(CHROMEOS_IME_CONSTANTS) extern const base::FilePath::CharType kInputMethodsDirName[];
diff --git a/chromeos/services/ime/decoder/decoder_engine.cc b/chromeos/services/ime/decoder/decoder_engine.cc index 6cddb2a..11f959f 100644 --- a/chromeos/services/ime/decoder/decoder_engine.cc +++ b/chromeos/services/ime/decoder/decoder_engine.cc
@@ -8,7 +8,6 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "chromeos/services/ime/constants.h" -#include "chromeos/services/ime/public/cpp/buildflags.h" namespace chromeos { namespace ime {
diff --git a/chromeos/services/ime/decoder/system_engine.cc b/chromeos/services/ime/decoder/system_engine.cc index 36f25e23..7b5f577 100644 --- a/chromeos/services/ime/decoder/system_engine.cc +++ b/chromeos/services/ime/decoder/system_engine.cc
@@ -9,7 +9,6 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "chromeos/services/ime/constants.h" -#include "chromeos/services/ime/public/cpp/buildflags.h" namespace chromeos { namespace ime {
diff --git a/chromeos/services/ime/ime_sandbox_hook.cc b/chromeos/services/ime/ime_sandbox_hook.cc index c930ad4..8b6f3ed 100644 --- a/chromeos/services/ime/ime_sandbox_hook.cc +++ b/chromeos/services/ime/ime_sandbox_hook.cc
@@ -10,10 +10,8 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/logging.h" -#include "build/buildflag.h" #include "chromeos/services/ime/constants.h" #include "chromeos/services/ime/ime_decoder.h" -#include "chromeos/services/ime/public/cpp/buildflags.h" #include "sandbox/linux/syscall_broker/broker_command.h" #include "sandbox/linux/syscall_broker/broker_file_permission.h" @@ -24,15 +22,6 @@ namespace ime { namespace { -// Whether IME instance shares a same language data path with each other. -inline constexpr bool CrosImeSharedDataEnabled() { -#if BUILDFLAG(ENABLE_CROS_IME_SHARED_DATA) - return true; -#else - return false; -#endif -} - void AddBundleFolder(std::vector<BrokerFilePermission>* permissions) { base::FilePath bundle_dir = base::FilePath(kBundledInputMethodsDirPath).AsEndingWithSeparator(); @@ -40,21 +29,6 @@ BrokerFilePermission::ReadOnlyRecursive(bundle_dir.value())); } -void AddSharedDataFolderIfEnabled( - std::vector<BrokerFilePermission>* permissions) { - if (!CrosImeSharedDataEnabled()) - return; - - // Without access to shared home folder, IME servcie will download all - // missing dictionaries to `kUserInputMethodsDirPath` of the current user. - base::FilePath shared_path = - base::FilePath(kSharedInputMethodsDirPath).AsEndingWithSeparator(); - if (base::CreateDirectory(shared_path)) { - permissions->push_back( - BrokerFilePermission::ReadWriteCreateRecursive(shared_path.value())); - } -} - void AddUserDataFolder(std::vector<BrokerFilePermission>* permissions) { // When failed to access user profile folder, decoder still can work, but // user dictionary can not be saved. @@ -79,7 +53,6 @@ AddBundleFolder(&permissions); AddUserDataFolder(&permissions); - AddSharedDataFolderIfEnabled(&permissions); return permissions; }
diff --git a/chromeos/services/ime/ime_service.cc b/chromeos/services/ime/ime_service.cc index 75e601bb0..7ee2a26 100644 --- a/chromeos/services/ime/ime_service.cc +++ b/chromeos/services/ime/ime_service.cc
@@ -16,11 +16,9 @@ #include "base/location.h" #include "base/notreached.h" #include "base/sequenced_task_runner.h" -#include "build/buildflag.h" #include "chromeos/services/ime/constants.h" #include "chromeos/services/ime/decoder/decoder_engine.h" #include "chromeos/services/ime/decoder/system_engine.h" -#include "chromeos/services/ime/public/cpp/buildflags.h" #include "chromeos/services/ime/rule_based_engine.h" #include "mojo/public/c/system/thunks.h" @@ -132,8 +130,6 @@ } const char* ImeService::GetImeGlobalDir() { - // Global IME data is supported yet. - NOTIMPLEMENTED(); return ""; }
diff --git a/chromeos/services/ime/ime_service.h b/chromeos/services/ime/ime_service.h index 8bf759c..8035f5a 100644 --- a/chromeos/services/ime/ime_service.h +++ b/chromeos/services/ime/ime_service.h
@@ -52,8 +52,10 @@ // ImeCrosPlatform overrides: const char* GetImeBundleDir() override; - const char* GetImeGlobalDir() override; const char* GetImeUserHomeDir() override; + // To be deprecated soon. Do not make a call on it anymore. + const char* GetImeGlobalDir() override; + int SimpleDownloadToFile(const char* url, const char* file_path, SimpleDownloadCallback callback) override;
diff --git a/chromeos/services/ime/public/cpp/BUILD.gn b/chromeos/services/ime/public/cpp/BUILD.gn index a145924..749076d 100644 --- a/chromeos/services/ime/public/cpp/BUILD.gn +++ b/chromeos/services/ime/public/cpp/BUILD.gn
@@ -3,16 +3,9 @@ # found in the LICENSE file. import("//build/buildflag_header.gni") -import("//chromeos/services/ime/public/features.gni") import("//testing/libfuzzer/fuzzer_test.gni") import("//third_party/libprotobuf-mutator/fuzzable_proto_library.gni") -buildflag_header("buildflags") { - header = "buildflags.h" - - flags = [ "ENABLE_CROS_IME_SHARED_DATA=$enable_cros_ime_shared_data" ] -} - source_set("structs") { sources = [ "suggestions.h" ] }
diff --git a/chromeos/services/ime/public/features.gni b/chromeos/services/ime/public/features.gni deleted file mode 100644 index 500a2fc..0000000 --- a/chromeos/services/ime/public/features.gni +++ /dev/null
@@ -1,8 +0,0 @@ -# Copyright 2018 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -declare_args() { - # Enable share language modules between IME services. - enable_cros_ime_shared_data = false -}
diff --git a/components/arc/arc_features.cc b/components/arc/arc_features.cc index f276dd5..4f6c09b 100644 --- a/components/arc/arc_features.cc +++ b/components/arc/arc_features.cc
@@ -79,7 +79,7 @@ // Controls ARCVM real time vcpu feature on a device with 2 logical cores // online. const base::Feature kRtVcpuDualCore{"ArcRtVcpuDualCore", - base::FEATURE_DISABLED_BY_DEFAULT}; + base::FEATURE_ENABLED_BY_DEFAULT}; // Controls ARCVM real time vcpu feature on a device with 3+ logical cores // online.
diff --git a/components/arc/arc_util.cc b/components/arc/arc_util.cc index 70481207..74adda1 100644 --- a/components/arc/arc_util.cc +++ b/components/arc/arc_util.cc
@@ -4,19 +4,25 @@ #include "components/arc/arc_util.h" +#include <sys/prctl.h> + #include <algorithm> #include <cstdio> +#include <set> #include "ash/constants/app_types.h" #include "ash/constants/ash_switches.h" #include "base/bind.h" #include "base/command_line.h" #include "base/feature_list.h" +#include "base/files/file_util.h" #include "base/logging.h" #include "base/process/launch.h" #include "base/process/process_metrics.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/system/sys_info.h" #include "chromeos/dbus/concierge/concierge_client.h" #include "chromeos/dbus/debug_daemon/debug_daemon_client.h" #include "chromeos/dbus/session_manager/session_manager_client.h" @@ -29,6 +35,12 @@ #include "ui/aura/window.h" #include "ui/display/types/display_constants.h" +// Downstream core scheduling interface for 4.19, 5.4 kernels. +// TODO(b/152605392): Remove once those kernel versions are obsolete. +#ifndef PR_SET_CORE_SCHED +#define PR_SET_CORE_SCHED 0x200 +#endif + namespace arc { namespace { @@ -482,4 +494,56 @@ } } +bool IsCoreSchedulingAvailable() { + static const bool has_vulns = []() { + // Check for support in the kernel by passing bad param `prctl(0x200, 2)`. + // If it is supported, we will get ERANGE rather than EINVAL. + // TODO(joelhockey): The public prctl(PR_SCHED_CORE, ...) API added in + // kernel 5.14 returns EINVAL rather than ERANGE for invalid params which + // doesn't give us any way to tell if core scheduling is supported. We + // should not remove PR_SET_CORE_SCHED 0x200 until all devices are at 5.14+. + if (prctl(PR_SET_CORE_SCHED, 2) == -1 && errno != ERANGE) { + VLOG(1) << "Core scheduling not supported in kernel"; + return false; + } + + base::FilePath sysfs_vulns("/sys/devices/system/cpu/vulnerabilities"); + std::string buf; + for (const std::string& s : {"l1tf", "mds"}) { + base::FilePath vuln = sysfs_vulns.Append(s); + if (!base::ReadFileToString(vuln, &buf)) { + LOG(ERROR) << "Could not read " << vuln; + continue; + } + base::TrimWhitespaceASCII(buf, base::TRIM_ALL, &buf); + if (buf != "Not affected") { + VLOG(1) << "Core scheduling available: " << vuln << "=" << buf; + return true; + } + } + VLOG(1) << "Core scheduling not required"; + return false; + }(); + return has_vulns; +} + +int NumberOfProcessorsForCoreScheduling() { + // cat /sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list |\ + // sort -u | wc -l + static const int num_physical_cores = []() { + std::set<std::string> lists; + std::string buf; + for (int i = 0; i < base::SysInfo::NumberOfProcessors(); ++i) { + base::FilePath list(base::StringPrintf( + "/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list", i)); + if (!base::ReadFileToString(list, &buf)) + LOG(ERROR) << "Could not read " << list; + else + lists.insert(buf); + } + return lists.size() > 0 ? lists.size() : 1; + }(); + return num_physical_cores; +} + } // namespace arc
diff --git a/components/arc/arc_util.h b/components/arc/arc_util.h index e8a0eeb..0130ed0c 100644 --- a/components/arc/arc_util.h +++ b/components/arc/arc_util.h
@@ -206,6 +206,18 @@ void ConfigureUpstartJobs(std::deque<JobDesc> jobs, chromeos::VoidDBusMethodCallback callback); +// Returns true if core scheduling is supported in the kernel, and CPU has MDS +// or L1TF vulnerabilities. Core scheduling does not run on CPUs that are not +// vulnerable. +// TODO(yusukes): Once https://crrev.com/c/3063740 is submitted, switch to the +// function in the CL and delete this. +bool IsCoreSchedulingAvailable(); + +// Returns number of physical cores. +// TODO(yusukes): Once https://crrev.com/c/3063740 is submitted, switch to the +// function in the CL and delete this. +int NumberOfProcessorsForCoreScheduling(); + } // namespace arc #endif // COMPONENTS_ARC_ARC_UTIL_H_
diff --git a/components/arc/ime/arc_ime_bridge_impl.cc b/components/arc/ime/arc_ime_bridge_impl.cc index aa3c796..4778168 100644 --- a/components/arc/ime/arc_ime_bridge_impl.cc +++ b/components/arc/ime/arc_ime_bridge_impl.cc
@@ -165,12 +165,6 @@ DVLOG(1) << "RequestHideIme is deprecated."; } -void ArcImeBridgeImpl::ShouldEnableKeyEventForwarding( - ShouldEnableKeyEventForwardingCallback callback) { - // TODO(b/190487153): Clean up this once the caller is removed. - std::move(callback).Run(true); -} - void ArcImeBridgeImpl::SendKeyEvent(std::unique_ptr<ui::KeyEvent> key_event, SendKeyEventCallback callback) { delegate_->SendKeyEvent(std::move(key_event), std::move(callback));
diff --git a/components/arc/ime/arc_ime_bridge_impl.h b/components/arc/ime/arc_ime_bridge_impl.h index b1553ef..55282e4 100644 --- a/components/arc/ime/arc_ime_bridge_impl.h +++ b/components/arc/ime/arc_ime_bridge_impl.h
@@ -53,8 +53,6 @@ const gfx::Range& selection_range, bool screen_coordinates) override; void RequestHideImeDeprecated() override; - void ShouldEnableKeyEventForwarding( - ShouldEnableKeyEventForwardingCallback callback) override; void SendKeyEvent(std::unique_ptr<ui::KeyEvent> key_event, SendKeyEventCallback callback) override;
diff --git a/components/arc/mojom/ime.mojom b/components/arc/mojom/ime.mojom index f0ca71b8..0e91a86 100644 --- a/components/arc/mojom/ime.mojom +++ b/components/arc/mojom/ime.mojom
@@ -110,9 +110,6 @@ // However, hiding can be canceled if a text field gets focus. [MinVersion=9] RequestHideImeDeprecated@5(); - // Asks Chrome whether the proxy IME should send a key event to the host IME. - [MinVersion=13] ShouldEnableKeyEventForwarding@6() => (bool should_forward); - // Sends a key event to Chrome to pass it to the Chrome OS IME. [MinVersion=14] SendKeyEvent@7(KeyEventData key_event_data) => (bool is_consumed);
diff --git a/components/arc/session/arc_vm_client_adapter.cc b/components/arc/session/arc_vm_client_adapter.cc index 5053c69..035e3eb 100644 --- a/components/arc/session/arc_vm_client_adapter.cc +++ b/components/arc/session/arc_vm_client_adapter.cc
@@ -822,13 +822,39 @@ void OnDemoResourcesLoaded(chromeos::VoidDBusMethodCallback callback, FileSystemStatus file_system_status) { + VLOG(2) << "Checking number of vcpus to spin up"; + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, + base::BindOnce(&ArcVmClientAdapter::GetNumVCPUsToStart, + start_params_.num_cores_disabled), + base::BindOnce(&ArcVmClientAdapter::OnGetNumVCPUsToStart, + weak_factory_.GetWeakPtr(), std::move(callback), + std::move(file_system_status))); + } + + static int32_t GetNumVCPUsToStart(uint32_t num_cores_disabled) { + // When the CPU has MDS or L1TF vulnerabilities, crosvm won't be allowed to + // run two vCPUs on the same physical core at the same time. This + // effectively disables SMT on crosvm. Because of this restriction, when the + // CPU has the vulnerabilities, set |cpus| to the number of physical cores. + // Otherwise, set the variable to the number of logical cores minus the ones + // disabled by chrome://flags/#scheduler-configuration. + // TODO(yusukes): Stop doing this on the other thread once we migrate to + // https://crrev.com/c/3063740. + const int32_t cpus = + IsCoreSchedulingAvailable() + ? NumberOfProcessorsForCoreScheduling() + : base::SysInfo::NumberOfProcessors() - num_cores_disabled; + DCHECK_LT(0, cpus); + return cpus; + } + + void OnGetNumVCPUsToStart(chromeos::VoidDBusMethodCallback callback, + FileSystemStatus file_system_status, + int32_t cpus) { const base::FilePath demo_session_apps_path = demo_mode_delegate_->GetDemoAppsPath(); - const int32_t cpus = - base::SysInfo::NumberOfProcessors() - start_params_.num_cores_disabled; - DCHECK_LT(0, cpus); - std::vector<std::string> kernel_cmdline = GenerateKernelCmdline( start_params_, file_system_status, *is_dev_mode_, is_host_on_vm_, GetChromeOsChannelFromLsbRelease());
diff --git a/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java b/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java index 9ea828f..bf01e34 100644 --- a/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java +++ b/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java
@@ -189,11 +189,12 @@ }); ContactsPicker.showContactsPicker(mWindowAndroid, this, multiselect, includeNames, includeEmails, includeTel, includeAddresses, includeIcons, "example.com"); - }); - mSelectionDelegate = mDialog.getCategoryViewForTesting().getSelectionDelegateForTesting(); - if (!multiselect) mSelectionDelegate.setSingleSelectionMode(); - mSelectionDelegate.addObserver(this); + mSelectionDelegate = + mDialog.getCategoryViewForTesting().getSelectionDelegateForTesting(); + if (!multiselect) mSelectionDelegate.setSingleSelectionMode(); + mSelectionDelegate.addObserver(this); + }); } /**
diff --git a/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/AppModalPresenterTest.java b/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/AppModalPresenterTest.java index d11bcbf..d85ed8f8 100644 --- a/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/AppModalPresenterTest.java +++ b/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/AppModalPresenterTest.java
@@ -85,7 +85,7 @@ @After public void tearDown() { - sManager.destroy(); + TestThreadUtils.runOnUiThreadBlocking(sManager::destroy); } @Test
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java index 366cc33..9f8075e 100644 --- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java +++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java
@@ -42,26 +42,28 @@ @Before public void setUp() { mContext = InstrumentationRegistry.getInstrumentation().getContext(); - mModel = new PropertyModel.Builder(PromoCardProperties.ALL_KEYS).build(); + mModel = TestThreadUtils.runOnUiThreadBlockingNoException( + () -> new PropertyModel.Builder(PromoCardProperties.ALL_KEYS).build()); } @After public void tearDown() { - mPromoCardCoordinator.destroy(); + TestThreadUtils.runOnUiThreadBlocking(mPromoCardCoordinator::destroy); } private void setupCoordinator(@LayoutStyle int layoutStyle) { // TODO(https://crbug.com/1217140): Remove this theme wrapper after dummy ui activity // is based on material theme. For now we need the theme wrapper to inflate the layout; // because we are not setting our theme overlay for the test apk - ContextThemeWrapper wrapperColor = new ContextThemeWrapper( - mContext, org.chromium.components.browser_ui.widget.R.style.ColorOverlay); - ContextThemeWrapper wrapperTheme = new ContextThemeWrapper( - wrapperColor, org.chromium.components.browser_ui.widget.R.style.Theme_BrowserUI); - mPromoCardCoordinator = - new PromoCardCoordinator(wrapperTheme, mModel, "test-feature", layoutStyle); - mView = (PromoCardView) mPromoCardCoordinator.getView(); - + TestThreadUtils.runOnUiThreadBlocking(() -> { + ContextThemeWrapper wrapperColor = new ContextThemeWrapper( + mContext, org.chromium.components.browser_ui.widget.R.style.ColorOverlay); + ContextThemeWrapper wrapperTheme = new ContextThemeWrapper(wrapperColor, + org.chromium.components.browser_ui.widget.R.style.Theme_BrowserUI); + mPromoCardCoordinator = + new PromoCardCoordinator(wrapperTheme, mModel, "test-feature", layoutStyle); + mView = (PromoCardView) mPromoCardCoordinator.getView(); + }); Assert.assertNotNull("PromoCardView is null", mView); }
diff --git a/components/feed/core/v2/metrics_reporter.cc b/components/feed/core/v2/metrics_reporter.cc index 291b229..7d62370b 100644 --- a/components/feed/core/v2/metrics_reporter.cc +++ b/components/feed/core/v2/metrics_reporter.cc
@@ -446,6 +446,7 @@ case FeedUserActionType::kTappedUnfollowTryAgainOnSnackbar: case FeedUserActionType::kTappedGoToFeedPostFollowActiveHelp: case FeedUserActionType::kTappedDismissPostFollowActiveHelp: + case FeedUserActionType::kTappedDiscoverFeedPreview: // Nothing additional for these actions. Note that some of these are iOS // only.
diff --git a/components/feed/core/v2/public/common_enums.h b/components/feed/core/v2/public/common_enums.h index 8ebfd5b..fbb9b6c 100644 --- a/components/feed/core/v2/public/common_enums.h +++ b/components/feed/core/v2/public/common_enums.h
@@ -110,8 +110,11 @@ // After following an active web feed, the user tapped to dismiss the // post-follow help dialog. kTappedDismissPostFollowActiveHelp = 38, + // After long-pressing on the feed and seeing the preview, the user tapped + // on the preview. + kTappedDiscoverFeedPreview = 39, - kMaxValue = kTappedDismissPostFollowActiveHelp, + kMaxValue = kTappedDiscoverFeedPreview, }; // The requested order of the Feed content.
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerViewTest.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerViewTest.java index 360a4ce..4f88c326 100644 --- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerViewTest.java +++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerViewTest.java
@@ -90,15 +90,16 @@ @Test @MediumTest public void testSecondaryActionDirectCallback() { - PropertyModel propertyModel = - new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS) - .with(MessageBannerProperties.MESSAGE_IDENTIFIER, - MessageIdentifier.TEST_MESSAGE) - .with(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID, - android.R.drawable.ic_menu_add) - .with(MessageBannerProperties.ON_SECONDARY_ACTION, mSecondaryActionCallback) - .build(); TestThreadUtils.runOnUiThreadBlocking(() -> { + PropertyModel propertyModel = + new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS) + .with(MessageBannerProperties.MESSAGE_IDENTIFIER, + MessageIdentifier.TEST_MESSAGE) + .with(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID, + android.R.drawable.ic_menu_add) + .with(MessageBannerProperties.ON_SECONDARY_ACTION, + mSecondaryActionCallback) + .build(); PropertyModelChangeProcessor.create( propertyModel, mMessageBannerView, MessageBannerViewBinder::bind); }); @@ -114,17 +115,18 @@ @Test @MediumTest public void testSecondaryActionMenu() { - PropertyModel propertyModel = - new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS) - .with(MessageBannerProperties.MESSAGE_IDENTIFIER, - MessageIdentifier.TEST_MESSAGE) - .with(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID, - android.R.drawable.ic_menu_add) - .with(MessageBannerProperties.SECONDARY_BUTTON_MENU_TEXT, - SECONDARY_BUTTON_MENU_TEXT) - .with(MessageBannerProperties.ON_SECONDARY_ACTION, mSecondaryActionCallback) - .build(); TestThreadUtils.runOnUiThreadBlocking(() -> { + PropertyModel propertyModel = + new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS) + .with(MessageBannerProperties.MESSAGE_IDENTIFIER, + MessageIdentifier.TEST_MESSAGE) + .with(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID, + android.R.drawable.ic_menu_add) + .with(MessageBannerProperties.SECONDARY_BUTTON_MENU_TEXT, + SECONDARY_BUTTON_MENU_TEXT) + .with(MessageBannerProperties.ON_SECONDARY_ACTION, + mSecondaryActionCallback) + .build(); PropertyModelChangeProcessor.create( propertyModel, mMessageBannerView, MessageBannerViewBinder::bind); });
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesMetrics.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesMetrics.java index e27485e3..8fa70243 100644 --- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesMetrics.java +++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesMetrics.java
@@ -91,6 +91,8 @@ return "PermissionUpdate"; case MessageIdentifier.ADS_BLOCKED: return "AdsBlocked"; + case MessageIdentifier.DOWNLOAD_PROGRESS: + return "DownloadProgress"; default: return "Unknown"; }
diff --git a/components/messages/android/message_enums.h b/components/messages/android/message_enums.h index a340c6f..cfeb23b 100644 --- a/components/messages/android/message_enums.h +++ b/components/messages/android/message_enums.h
@@ -86,6 +86,7 @@ GROUPED_PERMISSION = 13, PERMISSION_UPDATE = 14, ADS_BLOCKED = 15, + DOWNLOAD_PROGRESS = 16, // Insert new values before this line. COUNT
diff --git a/components/metrics/log_store.h b/components/metrics/log_store.h index cb0f633..da81179 100644 --- a/components/metrics/log_store.h +++ b/components/metrics/log_store.h
@@ -16,6 +16,8 @@ // at a time by staging and discarding logs, and persist/load the whole set. class LogStore { public: + virtual ~LogStore() = default; + // Returns true if there are any logs waiting to be uploaded. virtual bool has_unsent_logs() const = 0;
diff --git a/components/metrics/metrics_log_store.cc b/components/metrics/metrics_log_store.cc index 99b9f6b..aa07d38 100644 --- a/components/metrics/metrics_log_store.cc +++ b/components/metrics/metrics_log_store.cc
@@ -57,49 +57,89 @@ break; case MetricsLog::ONGOING_LOG: case MetricsLog::INDEPENDENT_LOG: - ongoing_log_queue_.StoreLog(log_data, log_metadata); + has_alternate_ongoing_log_store() + ? alternate_ongoing_log_queue_->StoreLog(log_data, log_metadata) + : ongoing_log_queue_.StoreLog(log_data, log_metadata); break; } } +void MetricsLogStore::SetAlternateOngoingLogStore( + std::unique_ptr<UnsentLogStore> log_store) { + DCHECK(!has_alternate_ongoing_log_store()); + DCHECK(unsent_logs_loaded_); + alternate_ongoing_log_queue_ = std::move(log_store); + alternate_ongoing_log_queue_->LoadPersistedUnsentLogs(); +} + +void MetricsLogStore::UnsetAlternateOngoingLogStore() { + DCHECK(has_alternate_ongoing_log_store()); + alternate_ongoing_log_queue_->TrimAndPersistUnsentLogs(); + alternate_ongoing_log_queue_.reset(); +} + bool MetricsLogStore::has_unsent_logs() const { return initial_log_queue_.has_unsent_logs() || - ongoing_log_queue_.has_unsent_logs(); + ongoing_log_queue_.has_unsent_logs() || + alternate_ongoing_log_store_has_unsent_logs(); } bool MetricsLogStore::has_staged_log() const { return initial_log_queue_.has_staged_log() || - ongoing_log_queue_.has_staged_log(); + ongoing_log_queue_.has_staged_log() || + alternate_ongoing_log_store_has_staged_log(); } const std::string& MetricsLogStore::staged_log() const { - return initial_log_queue_.has_staged_log() ? initial_log_queue_.staged_log() - : ongoing_log_queue_.staged_log(); + return initial_log_queue_.has_staged_log() + ? initial_log_queue_.staged_log() + : ongoing_log_store()->staged_log(); } const std::string& MetricsLogStore::staged_log_hash() const { return initial_log_queue_.has_staged_log() ? initial_log_queue_.staged_log_hash() - : ongoing_log_queue_.staged_log_hash(); + : ongoing_log_store()->staged_log_hash(); } const std::string& MetricsLogStore::staged_log_signature() const { return initial_log_queue_.has_staged_log() ? initial_log_queue_.staged_log_signature() - : ongoing_log_queue_.staged_log_signature(); + : ongoing_log_store()->staged_log_signature(); } absl::optional<uint64_t> MetricsLogStore::staged_log_user_id() const { - // MetricsLogStore base class should never have any logs associated with a - // user ID. - return absl::nullopt; + return initial_log_queue_.has_staged_log() + ? initial_log_queue_.staged_log_user_id() + : ongoing_log_store()->staged_log_user_id(); +} + +bool MetricsLogStore::has_alternate_ongoing_log_store() const { + return alternate_ongoing_log_queue_ != nullptr; +} + +bool MetricsLogStore::alternate_ongoing_log_store_has_unsent_logs() const { + return has_alternate_ongoing_log_store() && + alternate_ongoing_log_queue_->has_unsent_logs(); +} + +bool MetricsLogStore::alternate_ongoing_log_store_has_staged_log() const { + return has_alternate_ongoing_log_store() && + alternate_ongoing_log_queue_->has_staged_log(); +} + +const UnsentLogStore* MetricsLogStore::ongoing_log_store() const { + return has_alternate_ongoing_log_store() ? alternate_ongoing_log_queue_.get() + : &ongoing_log_queue_; } void MetricsLogStore::StageNextLog() { DCHECK(!has_staged_log()); if (initial_log_queue_.has_unsent_logs()) initial_log_queue_.StageNextLog(); - else + else if (alternate_ongoing_log_store_has_unsent_logs()) + alternate_ongoing_log_queue_->StageNextLog(); + else if (ongoing_log_queue_.has_unsent_logs()) ongoing_log_queue_.StageNextLog(); } @@ -107,8 +147,11 @@ DCHECK(has_staged_log()); if (initial_log_queue_.has_staged_log()) initial_log_queue_.DiscardStagedLog(); - else + else if (alternate_ongoing_log_store_has_staged_log()) + alternate_ongoing_log_queue_->DiscardStagedLog(); + else if (ongoing_log_queue_.has_staged_log()) ongoing_log_queue_.DiscardStagedLog(); + DCHECK(!has_staged_log()); } @@ -116,7 +159,9 @@ DCHECK(has_staged_log()); if (initial_log_queue_.has_staged_log()) initial_log_queue_.MarkStagedLogAsSent(); - else + else if (alternate_ongoing_log_store_has_staged_log()) + alternate_ongoing_log_queue_->MarkStagedLogAsSent(); + else if (ongoing_log_queue_.has_staged_log()) ongoing_log_queue_.MarkStagedLogAsSent(); } @@ -127,6 +172,8 @@ initial_log_queue_.TrimAndPersistUnsentLogs(); ongoing_log_queue_.TrimAndPersistUnsentLogs(); + if (has_alternate_ongoing_log_store()) + alternate_ongoing_log_queue_->TrimAndPersistUnsentLogs(); } } // namespace metrics
diff --git a/components/metrics/metrics_log_store.h b/components/metrics/metrics_log_store.h index f17590f..6c07f28 100644 --- a/components/metrics/metrics_log_store.h +++ b/components/metrics/metrics_log_store.h
@@ -5,6 +5,7 @@ #ifndef COMPONENTS_METRICS_METRICS_LOG_STORE_H_ #define COMPONENTS_METRICS_METRICS_LOG_STORE_H_ +#include <memory> #include <string> #include "base/macros.h" @@ -25,6 +26,14 @@ // This implementation keeps track of two types of logs, initial and ongoing, // each stored in UnsentLogStore. It prioritizes staging initial logs over // ongoing logs. +// +// An alternate log store can be set to persist ongoing logs. For example, this +// can be used to separate user logs from device logs on Chrome OS. If set, all +// ongoing logs will be written to this alternate log store. Ongoing logs from +// the alternate log store will be prioritized over ongoing logs from the native +// ongoing log store when logs are staged. If an alternate log store is bound, +// then logs will be prioritized in the following order: initial, alternate +// ongoing, native ongoing. class MetricsLogStore : public LogStore { public: // Configurable limits for ensuring and restricting local log storage. @@ -56,7 +65,7 @@ MetricsLogStore(PrefService* local_state, StorageLimits storage_limits, const std::string& signing_key); - ~MetricsLogStore(); + ~MetricsLogStore() override; // Registers local state prefs used by this class. static void RegisterPrefs(PrefRegistrySimple* registry); @@ -66,6 +75,24 @@ MetricsLog::LogType log_type, const LogMetadata& log_metadata); + // Binds an alternate log store to be managed by |this|. All ongoing logs + // after this call will be written to |log_store| until it is unset. Only one + // alternate log store can be bound at a time. Returns true if log store is + // bound successfully. + // + // If an alternate log store is already bound, this function will not bind + // |log_store| and return false. + // + // This should be called after |LoadPersistedUnsentLogs()| and after + // initialization. + void SetAlternateOngoingLogStore(std::unique_ptr<UnsentLogStore> log_store); + + // Unsets the alternate log store by flushing all existing logs to persistent + // storage before destructing the alternate log store. + // + // If no alternate log store is bound, then this function no-ops. + void UnsetAlternateOngoingLogStore(); + // LogStore: bool has_unsent_logs() const override; bool has_staged_log() const override; @@ -84,6 +111,18 @@ size_t initial_log_count() const { return initial_log_queue_.size(); } private: + // Returns true if alternate log store is set and it has unsent logs. + bool alternate_ongoing_log_store_has_unsent_logs() const; + + // Returns true if alternate log store is set and it has a staged log. + bool alternate_ongoing_log_store_has_staged_log() const; + + // Returns true if alternate log store is set. + bool has_alternate_ongoing_log_store() const; + + // Returns the current ongoing log store being used. + const UnsentLogStore* ongoing_log_store() const; + // Tracks whether unsent logs (if any) have been loaded from the serializer. bool unsent_logs_loaded_; @@ -92,6 +131,10 @@ UnsentLogStore initial_log_queue_; // Logs stored with the ONGOING_LOG type that haven't been sent yet. UnsentLogStore ongoing_log_queue_; + // Alternate place to store logs stored with ONGOING_LOG type that haven't + // been sent yet. If initialized, all logs of type ONGOING_LOG will be stored + // here instead of |ongoing_log_queue_|. + std::unique_ptr<UnsentLogStore> alternate_ongoing_log_queue_; DISALLOW_COPY_AND_ASSIGN(MetricsLogStore); };
diff --git a/components/metrics/metrics_log_store_unittest.cc b/components/metrics/metrics_log_store_unittest.cc index 71647cf..5a3b16e 100644 --- a/components/metrics/metrics_log_store_unittest.cc +++ b/components/metrics/metrics_log_store_unittest.cc
@@ -6,6 +6,8 @@ #include "components/metrics/metrics_pref_names.h" #include "components/metrics/test/test_metrics_service_client.h" +#include "components/metrics/unsent_log_store_metrics_impl.h" +#include "components/prefs/pref_registry_simple.h" #include "components/prefs/testing_pref_service.h" #include "testing/gtest/include/gtest/gtest.h" @@ -13,10 +15,34 @@ namespace { +const char kTestPrefName[] = "TestPref"; + +class TestUnsentLogStore : public UnsentLogStore { + public: + explicit TestUnsentLogStore(PrefService* service) + : UnsentLogStore(std::make_unique<UnsentLogStoreMetricsImpl>(), + service, + kTestPrefName, + nullptr, + /* min_log_count= */ 3, + /* min_log_bytes= */ 1, + /* max_log_size= */ 0, + std::string()) {} + ~TestUnsentLogStore() override = default; + + TestUnsentLogStore(const TestUnsentLogStore&) = delete; + TestUnsentLogStore& operator=(const TestUnsentLogStore&) = delete; + + static void RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterListPref(kTestPrefName); + } +}; + class MetricsLogStoreTest : public testing::Test { public: MetricsLogStoreTest() { MetricsLogStore::RegisterPrefs(pref_service_.registry()); + TestUnsentLogStore::RegisterPrefs(pref_service_.registry()); } ~MetricsLogStoreTest() override {} @@ -201,4 +227,101 @@ EXPECT_EQ(0U, log_store.initial_log_count()); } +TEST_F(MetricsLogStoreTest, WritesToAlternateOngoingLogStore) { + MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(), + std::string()); + std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store = + std::make_unique<TestUnsentLogStore>(&pref_service_); + TestUnsentLogStore* alternate_ongoing_log_store_ptr = + alternate_ongoing_log_store.get(); + + // Needs to be called before writing logs to alternate ongoing store since + // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that + // the native initial and ongoing unsent logs have already been loaded. + log_store.LoadPersistedUnsentLogs(); + + log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata()); + log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store)); + log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata()); + log_store.StoreLog("c", MetricsLog::ONGOING_LOG, LogMetadata()); + + EXPECT_EQ(1U, log_store.ongoing_log_count()); + EXPECT_EQ(2U, alternate_ongoing_log_store_ptr->size()); +} + +TEST_F(MetricsLogStoreTest, StagesInitialOverBothOngoing) { + MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(), + std::string()); + std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store = + std::make_unique<TestUnsentLogStore>(&pref_service_); + TestUnsentLogStore* alternate_ongoing_log_store_ptr = + alternate_ongoing_log_store.get(); + + // Needs to be called before writing logs to alternate ongoing store since + // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that + // the native initial and ongoing unsent logs have already been loaded. + log_store.LoadPersistedUnsentLogs(); + + log_store.StoreLog("a", MetricsLog::INITIAL_STABILITY_LOG, LogMetadata()); + log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata()); + log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store)); + log_store.StoreLog("c", MetricsLog::ONGOING_LOG, LogMetadata()); + log_store.StageNextLog(); + log_store.DiscardStagedLog(); + + // Discarded log should be from initial_log_store. + EXPECT_EQ(0U, log_store.initial_log_count()); + EXPECT_EQ(1U, log_store.ongoing_log_count()); + EXPECT_EQ(1U, alternate_ongoing_log_store_ptr->size()); +} + +TEST_F(MetricsLogStoreTest, StagesAlternateOverOngoing) { + MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(), + std::string()); + std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store = + std::make_unique<TestUnsentLogStore>(&pref_service_); + TestUnsentLogStore* alternate_ongoing_log_store_ptr = + alternate_ongoing_log_store.get(); + + // Needs to be called before writing logs to alternate ongoing store since + // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that + // the native initial and ongoing unsent logs have already been loaded. + log_store.LoadPersistedUnsentLogs(); + + log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata()); + log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store)); + log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata()); + log_store.StageNextLog(); + log_store.DiscardStagedLog(); + + // Discarded log should be from alternate_ongoing_log_store. + EXPECT_EQ(1U, log_store.ongoing_log_count()); + EXPECT_EQ(0U, alternate_ongoing_log_store_ptr->size()); +} + +TEST_F(MetricsLogStoreTest, + UnboundAlternateOngoingLogStoreWritesToNativeOngoing) { + MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(), + std::string()); + std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store = + std::make_unique<TestUnsentLogStore>(&pref_service_); + + // Needs to be called before writing logs to alternate ongoing store since + // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that + // the native initial and ongoing unsent logs have already been loaded. + log_store.LoadPersistedUnsentLogs(); + + log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store)); + // Should be written to alternate ongoing log store. + log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata()); + + log_store.UnsetAlternateOngoingLogStore(); + + // Should be in native ongoing log store. + log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata()); + log_store.StoreLog("c", MetricsLog::ONGOING_LOG, LogMetadata()); + + EXPECT_EQ(2U, log_store.ongoing_log_count()); +} + } // namespace metrics
diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc index bd6f3da..97170e0f 100644 --- a/components/metrics/metrics_service.cc +++ b/components/metrics/metrics_service.cc
@@ -434,6 +434,28 @@ delegating_provider_.ClearSavedStabilityMetrics(); } +#if defined(OS_CHROMEOS) +void MetricsService::SetUserLogStore( + std::unique_ptr<UnsentLogStore> user_log_store) { + // This should only be set after the service has finished initializing. + DCHECK_EQ(SENDING_LOGS, state_); + + // Closes the current log so that a new log can be opened in the user log + // store. + PushPendingLogsToPersistentStorage(); + log_store()->SetAlternateOngoingLogStore(std::move(user_log_store)); + OpenNewLog(); +} + +void MetricsService::UnsetUserLogStore() { + DCHECK_EQ(SENDING_LOGS, state_); + // Pushes all the existing logs to user log store before unbound. + PushPendingLogsToPersistentStorage(); + log_store()->UnsetAlternateOngoingLogStore(); + OpenNewLog(); +} +#endif + bool MetricsService::StageCurrentLogForTest() { CloseCurrentLog(); @@ -450,7 +472,6 @@ // private methods //------------------------------------------------------------------------------ - //------------------------------------------------------------------------------ // Initialization methods @@ -654,8 +675,7 @@ // Even if reporting is disabled, the scheduler is needed to trigger the // creation of the initial log, which must be done in order for any logs to be // persisted on shutdown or backgrounding. - if (recording_active() && - (reporting_active() || state_ < SENDING_LOGS)) { + if (recording_active() && (reporting_active() || state_ < SENDING_LOGS)) { rotation_scheduler_->Start(); reporting_service_.Start(); } @@ -673,8 +693,7 @@ // that has to happen in order for logs to be cut and stored when persisting. // TODO(stuartmorgan): Call Stop() on the scheduler when reporting and/or // recording are turned off instead of letting it fire and then aborting. - if (idle_since_last_transmission_ || - !recording_active() || + if (idle_since_last_transmission_ || !recording_active() || (!reporting_active() && state_ >= SENDING_LOGS)) { rotation_scheduler_->Stop(); rotation_scheduler_->RotationFinished();
diff --git a/components/metrics/metrics_service.h b/components/metrics/metrics_service.h index fd126fa..f5bc248b 100644 --- a/components/metrics/metrics_service.h +++ b/components/metrics/metrics_service.h
@@ -42,7 +42,7 @@ namespace base { class HistogramSamples; class PrefService; -} +} // namespace base namespace metrics { @@ -158,6 +158,17 @@ // Clears the stability metrics that are saved in local state. void ClearSavedStabilityMetrics(); +#if defined(OS_CHROMEOS) + // Binds a user log store to store unsent logs. This log store will be + // fully managed by MetricsLogStore. This will no-op if another log store has + // already been set. + void SetUserLogStore(std::unique_ptr<UnsentLogStore> user_log_store); + + // Unbinds the user log store. If there was no user log store, then this does + // nothing. + void UnsetUserLogStore(); +#endif + variations::SyntheticTrialRegistry* synthetic_trial_registry() { return &synthetic_trial_registry_; } @@ -209,7 +220,7 @@ enum RecordingState { INACTIVE, ACTIVE, - UNSET + UNSET, }; // Gets the LogStore for UMA logs.
diff --git a/components/metrics/metrics_service_client.h b/components/metrics/metrics_service_client.h index 5607a30..7015abd0 100644 --- a/components/metrics/metrics_service_client.h +++ b/components/metrics/metrics_service_client.h
@@ -116,6 +116,9 @@ // data. virtual bool ShouldStartUpFastForTesting() const; + // Called when loading state changed, e.g. start/stop loading. + virtual void LoadingStateChanged(bool is_loading) {} + // Called on plugin loading errors. virtual void OnPluginLoadingError(const base::FilePath& plugin_path) {}
diff --git a/components/metrics/reporting_service_unittest.cc b/components/metrics/reporting_service_unittest.cc index 47f2ec6..adac9f7 100644 --- a/components/metrics/reporting_service_unittest.cc +++ b/components/metrics/reporting_service_unittest.cc
@@ -43,8 +43,8 @@ class TestLogStore : public LogStore { public: - TestLogStore() {} - ~TestLogStore() {} + TestLogStore() = default; + ~TestLogStore() override = default; void AddLog(const TestLog& log) { logs_.push_back(log); }
diff --git a/components/metrics/structured/structured_metrics_provider.h b/components/metrics/structured/structured_metrics_provider.h index b26d97c..5364e3d8 100644 --- a/components/metrics/structured/structured_metrics_provider.h +++ b/components/metrics/structured/structured_metrics_provider.h
@@ -6,7 +6,6 @@ #define COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_PROVIDER_H_ #include <memory> -#include <vector> #include "base/files/file_path.h" #include "base/memory/scoped_refptr.h"
diff --git a/components/metrics/unsent_log_store.h b/components/metrics/unsent_log_store.h index 3107650..b3d7a2e 100644 --- a/components/metrics/unsent_log_store.h +++ b/components/metrics/unsent_log_store.h
@@ -55,7 +55,7 @@ size_t min_log_bytes, size_t max_log_size, const std::string& signing_key); - ~UnsentLogStore(); + ~UnsentLogStore() override; // LogStore: bool has_unsent_logs() const override;
diff --git a/components/metrics_services_manager/metrics_services_manager.cc b/components/metrics_services_manager/metrics_services_manager.cc index d404a43..db25924f 100644 --- a/components/metrics_services_manager/metrics_services_manager.cc +++ b/components/metrics_services_manager/metrics_services_manager.cc
@@ -54,6 +54,11 @@ return variations_service_.get(); } +void MetricsServicesManager::LoadingStateChanged(bool is_loading) { + DCHECK(thread_checker_.CalledOnValidThread()); + GetMetricsServiceClient()->LoadingStateChanged(is_loading); +} + void MetricsServicesManager::OnPluginLoadingError( const base::FilePath& plugin_path) { GetMetricsServiceClient()->OnPluginLoadingError(plugin_path);
diff --git a/components/metrics_services_manager/metrics_services_manager.h b/components/metrics_services_manager/metrics_services_manager.h index d289602..501da0d 100644 --- a/components/metrics_services_manager/metrics_services_manager.h +++ b/components/metrics_services_manager/metrics_services_manager.h
@@ -62,6 +62,9 @@ // Returns the VariationsService, creating it if it hasn't been created yet. variations::VariationsService* GetVariationsService(); + // Called when loading state changed. + void LoadingStateChanged(bool is_loading); + // Should be called when a plugin loading error occurs. void OnPluginLoadingError(const base::FilePath& plugin_path);
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc index a6e5077..f9394578 100644 --- a/components/omnibox/common/omnibox_features.cc +++ b/components/omnibox/common/omnibox_features.cc
@@ -256,7 +256,7 @@ // Feature used to enable the second batch of Pedals (Safety Check, etc.). const base::Feature kOmniboxPedalsBatch2{"OmniboxPedalsBatch2", - base::FEATURE_DISABLED_BY_DEFAULT}; + base::FEATURE_ENABLED_BY_DEFAULT}; // Feature used to enable the second batch of Pedals (Safety Check, etc.) // for non-English locales (English locales are 'en' and 'en-GB'). @@ -270,7 +270,7 @@ // Feature that enables use of the colored version of the default Pedal icon. const base::Feature kOmniboxPedalsDefaultIconColored{ - "OmniboxPedalsDefaultIconColored", base::FEATURE_DISABLED_BY_DEFAULT}; + "OmniboxPedalsDefaultIconColored", base::FEATURE_ENABLED_BY_DEFAULT}; // Feature that enables loading synonyms from the translation console. const base::Feature kOmniboxPedalsTranslationConsole{
diff --git a/components/omnibox/resources/omnibox_pedal_synonyms.grd b/components/omnibox/resources/omnibox_pedal_synonyms.grd index 838d391..70cbc558 100644 --- a/components/omnibox/resources/omnibox_pedal_synonyms.grd +++ b/components/omnibox/resources/omnibox_pedal_synonyms.grd
@@ -113,11 +113,15 @@ <output filename="omnibox_pedal_synonyms_en-XA.pak" type="data_package" lang="en-XA" /> </outputs> + <!-- Note, the list of languages here must match the omnibox_pedal_synonyms_grd languages + list in tools/gritsettings/translation_expectations.pyl or TC pipeline breaks. --> <translations> <file path="translations/omnibox_pedal_synonyms_ar.xtb" lang="ar" /> + <file path="translations/omnibox_pedal_synonyms_de.xtb" lang="de" /> <file path="translations/omnibox_pedal_synonyms_es-419.xtb" lang="es-419" /> <file path="translations/omnibox_pedal_synonyms_fr.xtb" lang="fr" /> <file path="translations/omnibox_pedal_synonyms_ja.xtb" lang="ja" /> + <file path="translations/omnibox_pedal_synonyms_zh-CN.xtb" lang="zh-CN" /> <file path="translations/omnibox_pedal_synonyms_zh-TW.xtb" lang="zh-TW" /> </translations>
diff --git a/components/omnibox/resources/translations/omnibox_pedal_synonyms_de.xtb b/components/omnibox/resources/translations/omnibox_pedal_synonyms_de.xtb new file mode 100644 index 0000000..91de7f51 --- /dev/null +++ b/components/omnibox/resources/translations/omnibox_pedal_synonyms_de.xtb
@@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="de"> +</translationbundle>
diff --git a/components/omnibox/resources/translations/omnibox_pedal_synonyms_zh-CN.xtb b/components/omnibox/resources/translations/omnibox_pedal_synonyms_zh-CN.xtb new file mode 100644 index 0000000..26e8b40 --- /dev/null +++ b/components/omnibox/resources/translations/omnibox_pedal_synonyms_zh-CN.xtb
@@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="zh-CN"> +</translationbundle>
diff --git a/components/optimization_guide/core/optimization_guide_navigation_data.cc b/components/optimization_guide/core/optimization_guide_navigation_data.cc index 65a8e1e..2c807bb 100644 --- a/components/optimization_guide/core/optimization_guide_navigation_data.cc +++ b/components/optimization_guide/core/optimization_guide_navigation_data.cc
@@ -14,8 +14,9 @@ #include "services/metrics/public/cpp/ukm_source_id.h" OptimizationGuideNavigationData::OptimizationGuideNavigationData( - int64_t navigation_id) - : navigation_id_(navigation_id) {} + int64_t navigation_id, + base::TimeTicks navigation_start) + : navigation_id_(navigation_id), navigation_start_(navigation_start) {} OptimizationGuideNavigationData::~OptimizationGuideNavigationData() { RecordMetrics();
diff --git a/components/optimization_guide/core/optimization_guide_navigation_data.h b/components/optimization_guide/core/optimization_guide_navigation_data.h index bfb87f55..2a3ed6f 100644 --- a/components/optimization_guide/core/optimization_guide_navigation_data.h +++ b/components/optimization_guide/core/optimization_guide_navigation_data.h
@@ -17,12 +17,14 @@ #include "components/optimization_guide/proto/hints.pb.h" #include "components/optimization_guide/proto/models.pb.h" #include "third_party/abseil-cpp/absl/types/optional.h" +#include "url/gurl.h" // A representation of optimization guide information related to a navigation. // Metrics will be recorded upon this object's destruction. class OptimizationGuideNavigationData { public: - explicit OptimizationGuideNavigationData(int64_t navigation_id); + OptimizationGuideNavigationData(int64_t navigation_id, + base::TimeTicks navigation_start); ~OptimizationGuideNavigationData(); OptimizationGuideNavigationData( @@ -34,10 +36,20 @@ return weak_ptr_factory_.GetWeakPtr(); } - // The navigation ID of the navigation handle that this data is associated - // with. + // The navigation ID of the navigation handle that this data is + // associated with. int64_t navigation_id() const { return navigation_id_; } + // The time of navigation start. + base::TimeTicks navigation_start() const { return navigation_start_; } + + // The navigation URL of the navigation handle that this data is + // associated with. + GURL navigation_url() const { return navigation_url_; } + void set_navigation_url(const GURL& navigation_url) { + navigation_url_ = navigation_url; + } + // The optimization types that were registered at the start of the navigation. base::flat_set<optimization_guide::proto::OptimizationType> registered_optimization_types() const { @@ -96,6 +108,13 @@ // with. const int64_t navigation_id_; + // The time of navigation start. + const base::TimeTicks navigation_start_; + + // The navigation URL of the navigation handle this data is associated with. + // Updated on navigation start and navigation redirects. + GURL navigation_url_; + // The optimization types that were registered at the start of the navigation. base::flat_set<optimization_guide::proto::OptimizationType> registered_optimization_types_;
diff --git a/components/optimization_guide/core/optimization_guide_navigation_data_unittest.cc b/components/optimization_guide/core/optimization_guide_navigation_data_unittest.cc index aad2a59..887d921 100644 --- a/components/optimization_guide/core/optimization_guide_navigation_data_unittest.cc +++ b/components/optimization_guide/core/optimization_guide_navigation_data_unittest.cc
@@ -25,7 +25,8 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; std::unique_ptr<OptimizationGuideNavigationData> data = - std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3); + std::make_unique<OptimizationGuideNavigationData>( + /*navigation_id=*/3, /*navigation_start=*/base::TimeTicks::Now()); data.reset(); // Make sure no UKM recorded. @@ -41,7 +42,8 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; std::unique_ptr<OptimizationGuideNavigationData> data = - std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3); + std::make_unique<OptimizationGuideNavigationData>( + /*navigation_id=*/3, /*navigation_start=*/base::TimeTicks::Now()); data->set_registered_optimization_types( {optimization_guide::proto::NOSCRIPT, optimization_guide::proto::RESOURCE_LOADING}); @@ -67,7 +69,8 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; std::unique_ptr<OptimizationGuideNavigationData> data = - std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3); + std::make_unique<OptimizationGuideNavigationData>( + /*navigation_id=*/3, /*navigation_start=*/base::TimeTicks::Now()); data->set_registered_optimization_targets( {optimization_guide::proto::OPTIMIZATION_TARGET_UNKNOWN, optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD}); @@ -93,7 +96,8 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; std::unique_ptr<OptimizationGuideNavigationData> data = - std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3); + std::make_unique<OptimizationGuideNavigationData>( + /*navigation_id=*/3, /*navigation_start=*/base::TimeTicks::Now()); data->set_hints_fetch_attempt_status( optimization_guide::RaceNavigationFetchAttemptStatus:: kRaceNavigationFetchHost); @@ -119,9 +123,10 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; - std::unique_ptr<OptimizationGuideNavigationData> data = - std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3); base::TimeTicks now = base::TimeTicks::Now(); + std::unique_ptr<OptimizationGuideNavigationData> data = + std::make_unique<OptimizationGuideNavigationData>( + /*navigation_id=*/3, /*navigation_start=*/now); data->set_hints_fetch_start(now); data->set_hints_fetch_end(now + base::TimeDelta::FromMilliseconds(123)); data.reset(); @@ -145,9 +150,10 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; - std::unique_ptr<OptimizationGuideNavigationData> data = - std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3); base::TimeTicks now = base::TimeTicks::Now(); + std::unique_ptr<OptimizationGuideNavigationData> data = + std::make_unique<OptimizationGuideNavigationData>( + /*navigation_id=*/3, /*navigation_start=*/now); data->set_hints_fetch_end(now); data.reset(); @@ -162,9 +168,10 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; - std::unique_ptr<OptimizationGuideNavigationData> data = - std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3); base::TimeTicks now = base::TimeTicks::Now(); + std::unique_ptr<OptimizationGuideNavigationData> data = + std::make_unique<OptimizationGuideNavigationData>( + /*navigation_id=*/3, /*navigation_start=*/now); data->set_hints_fetch_start(now); data.reset(); @@ -187,9 +194,10 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; - std::unique_ptr<OptimizationGuideNavigationData> data = - std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3); base::TimeTicks now = base::TimeTicks::Now(); + std::unique_ptr<OptimizationGuideNavigationData> data = + std::make_unique<OptimizationGuideNavigationData>( + /*navigation_id=*/3, /*navigation_start=*/now); data->set_hints_fetch_start(now); data->set_hints_fetch_end(now - base::TimeDelta::FromMilliseconds(123)); data.reset();
diff --git a/components/security_interstitials/content/ssl_error_navigation_throttle.cc b/components/security_interstitials/content/ssl_error_navigation_throttle.cc index 3f55f62..d9011de0 100644 --- a/components/security_interstitials/content/ssl_error_navigation_throttle.cc +++ b/components/security_interstitials/content/ssl_error_navigation_throttle.cc
@@ -93,6 +93,12 @@ // through the interstitial will continue the navigation in a regular browser // window. if (std::move(is_in_hosted_app_callback_).Run(handle->GetWebContents())) { + // Non-primary pages (e.g. prerendering, fenced-frame, portal) should not + // be handled as a hosted app since they are associated with the non-app + // WebContents. For prerendering specifically, we should already have + // canceled the prerender from OnSSLCertificateError before the throttle + // runs WillProcessResponse. + DCHECK(navigation_handle()->IsInPrimaryMainFrame()); QueueShowInterstitial( std::move(handle_ssl_error_callback_), handle->GetWebContents(), // The navigation handle's net error code will be @@ -138,6 +144,11 @@ std::string error_page_content = blocking_page->GetHTMLContents(); content::NavigationHandle* handle = navigation_handle(); + + // For non-primary pages (e.g. prerendering) we should already have canceled + // the prerender from OnSSLCertificateError before the throttle failure. + DCHECK(handle->IsInPrimaryMainFrame()); + security_interstitials::SecurityInterstitialTabHelper::AssociateBlockingPage( handle->GetWebContents(), handle->GetNavigationId(), std::move(blocking_page));
diff --git a/components/services/storage/public/cpp/buckets/bucket_info.cc b/components/services/storage/public/cpp/buckets/bucket_info.cc index 9ae1f838..161a883 100644 --- a/components/services/storage/public/cpp/buckets/bucket_info.cc +++ b/components/services/storage/public/cpp/buckets/bucket_info.cc
@@ -35,4 +35,8 @@ return !(lhs == rhs); } +bool operator<(const BucketInfo& lhs, const BucketInfo& rhs) { + return lhs.id < rhs.id; +} + } // namespace storage
diff --git a/components/services/storage/public/cpp/buckets/bucket_info.h b/components/services/storage/public/cpp/buckets/bucket_info.h index 6b17d2d..b7154b2 100644 --- a/components/services/storage/public/cpp/buckets/bucket_info.h +++ b/components/services/storage/public/cpp/buckets/bucket_info.h
@@ -36,6 +36,9 @@ COMPONENT_EXPORT(STORAGE_SERVICE_BUCKETS_SUPPORT) friend bool operator!=(const BucketInfo& lhs, const BucketInfo& rhs); + COMPONENT_EXPORT(STORAGE_SERVICE_BUCKETS_SUPPORT) + friend bool operator<(const BucketInfo& lhs, const BucketInfo& rhs); + bool is_default() const { return name == kDefaultBucketName; } BucketId id;
diff --git a/components/translate/core/browser/mock_translate_metrics_logger.h b/components/translate/core/browser/mock_translate_metrics_logger.h index 2a0d864..936833a 100644 --- a/components/translate/core/browser/mock_translate_metrics_logger.h +++ b/components/translate/core/browser/mock_translate_metrics_logger.h
@@ -52,7 +52,7 @@ void(const std::string&, TranslateBrowserMetrics::TargetLanguageOrigin)); MOCK_METHOD1(LogUIInteraction, void(UIInteraction)); - MOCK_METHOD0(GetNextManualTranslationType, TranslationType()); + MOCK_METHOD1(GetNextManualTranslationType, TranslationType(bool)); MOCK_METHOD1(SetHasHrefTranslateTarget, void(bool)); MOCK_METHOD1(LogWasContentEmpty, void(bool));
diff --git a/components/translate/core/browser/translate_infobar_delegate.cc b/components/translate/core/browser/translate_infobar_delegate.cc index 0ac5215..46b7f60 100644 --- a/components/translate/core/browser/translate_infobar_delegate.cc +++ b/components/translate/core/browser/translate_infobar_delegate.cc
@@ -267,7 +267,8 @@ translate_manager_->TranslatePage( source_language_code(), target_language_code(), false, translate_manager_->GetActiveTranslateMetricsLogger() - ->GetNextManualTranslationType()); + ->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); } bool TranslateInfoBarDelegate::ShouldShowMessageInfoBarButton() {
diff --git a/components/translate/core/browser/translate_manager.cc b/components/translate/core/browser/translate_manager.cc index 6cfbe41e..f5f8eaa 100644 --- a/components/translate/core/browser/translate_manager.cc +++ b/components/translate/core/browser/translate_manager.cc
@@ -362,7 +362,8 @@ if (auto_translate && !language_state_.IsPageTranslated()) { TranslatePage( source_code, target_lang, triggered_from_menu, - GetActiveTranslateMetricsLogger()->GetNextManualTranslationType()); + GetActiveTranslateMetricsLogger()->GetNextManualTranslationType( + triggered_from_menu)); return; }
diff --git a/components/translate/core/browser/translate_metrics_logger.h b/components/translate/core/browser/translate_metrics_logger.h index 61827e1..34df1fe 100644 --- a/components/translate/core/browser/translate_metrics_logger.h +++ b/components/translate/core/browser/translate_metrics_logger.h
@@ -41,29 +41,51 @@ // numeric values should never be reused. enum class TranslationStatus { kUninitialized = 0, - kSuccessFromManualTranslation = 1, + // kSuccessFromManualTranslation = 1, // no longer used, split into + // kSuccessFromManualUiTranslation and + // kSuccessFromManualContextMenuTranslation enum values. kSuccessFromAutomaticTranslationByPref = 2, kSuccessFromAutomaticTranslationByLink = 3, - kRevertedManualTranslation = 4, + // kManualTranslation = 4, // no longer used, split into + // kRevertedManualUiTranslation and + // kRevertedManualContextMenuTranslation enum values. kRevertedAutomaticTranslation = 5, kNewTranslation = 6, kTranslationAbandoned = 7, - kFailedWithNoErrorManualTranslation = 8, + // kFailedWithNoErrorManualTranslation = 8, // no longer used, split into + // kFailedWithNoErrorManualUiTranslation and + // kFailedWithNoErrorManualContextMenuTranslation enum values. kFailedWithNoErrorAutomaticTranslation = 9, - kFailedWithErrorManualTranslation = 10, + // kFailedWithErrorManualTranslation = 10, // no longer used, split into + // kFailedWithErrorManualUiTranslation and + // kFailedWithErrorManualContextMenuTranslation enum values. kFailedWithErrorAutomaticTranslation = 11, - kMaxValue = kFailedWithErrorAutomaticTranslation, + kSuccessFromManualUiTranslation = 12, + kRevertedManualUiTranslation = 13, + kFailedWithNoErrorManualUiTranslation = 14, + kFailedWithErrorManualUiTranslation = 15, + kSuccessFromManualContextMenuTranslation = 16, + kRevertedManualContextMenuTranslation = 17, + kFailedWithNoErrorManualContextMenuTranslation = 18, + kFailedWithErrorManualContextMenuTranslation = 19, + kMaxValue = kFailedWithErrorManualContextMenuTranslation, }; // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. enum class TranslationType { kUninitialized = 0, - kManualInitialTranslation = 1, - kManualReTranslation = 2, + // kManualInitialTranslation = 1, // no longer used, split into + // kManualUiInitialTranslation and kManualContextMenuInitialranslation + // kManualReTranslation = 2, // no longer used, split into + // kManualUiReTranslation and kManualContextMenuReTranslation kAutomaticTranslationByPref = 3, kAutomaticTranslationByLink = 4, - kMaxValue = kAutomaticTranslationByLink, + kManualUiInitialTranslation = 5, + kManualUiReTranslation = 6, + kManualContextMenuInitialTranslation = 7, + kManualContextMenuReTranslation = 8, + kMaxValue = kManualContextMenuReTranslation, }; // These values are persisted to logs. Entries should not be renumbered and @@ -174,7 +196,8 @@ virtual void LogUIInteraction(UIInteraction ui_interaction) = 0; // Returns the translation type of the next manual translation. - virtual TranslationType GetNextManualTranslationType() = 0; + virtual TranslationType GetNextManualTranslationType( + bool is_context_menu_initiated_translation) = 0; virtual void SetHasHrefTranslateTarget(bool has_href_translate_target) = 0;
diff --git a/components/translate/core/browser/translate_metrics_logger_impl.cc b/components/translate/core/browser/translate_metrics_logger_impl.cc index b36b189..7f606d2f 100644 --- a/components/translate/core/browser/translate_metrics_logger_impl.cc +++ b/components/translate/core/browser/translate_metrics_logger_impl.cc
@@ -75,7 +75,8 @@ const char kTranslateApplicationStartNeverTranslateSiteCount[] = "Translate.ApplicationStart.NeverTranslateSite.Count"; -TranslationType NullTranslateMetricsLogger::GetNextManualTranslationType() { +TranslationType NullTranslateMetricsLogger::GetNextManualTranslationType( + bool is_context_menu_initiated_translation) { return TranslationType::kUninitialized; } @@ -473,10 +474,19 @@ base::UmaHistogramEnumeration(kTranslateUiInteractionEvent, ui_interaction); } -TranslationType TranslateMetricsLoggerImpl::GetNextManualTranslationType() { +TranslationType TranslateMetricsLoggerImpl::GetNextManualTranslationType( + bool is_context_menu_initiated_translation) { + if (is_context_menu_initiated_translation) { + return has_any_translation_started_ + ? TranslationType::kManualContextMenuReTranslation + : TranslationType::kManualContextMenuInitialTranslation; + } + + // If the translation was not initiated from the context menu, then it must + // have been triggered from the translate UI. return has_any_translation_started_ - ? TranslationType::kManualReTranslation - : TranslationType::kManualInitialTranslation; + ? TranslationType::kManualUiReTranslation + : TranslationType::kManualUiInitialTranslation; } void TranslateMetricsLoggerImpl::SetHasHrefTranslateTarget( @@ -530,9 +540,13 @@ TranslationStatus TranslateMetricsLoggerImpl::ConvertTranslationTypeToRevertedTranslationStatus( TranslationType translation_type) { - if (translation_type == TranslationType::kManualInitialTranslation || - translation_type == TranslationType::kManualReTranslation) - return TranslationStatus::kRevertedManualTranslation; + if (translation_type == TranslationType::kManualUiInitialTranslation || + translation_type == TranslationType::kManualUiReTranslation) + return TranslationStatus::kRevertedManualUiTranslation; + if (translation_type == + TranslationType::kManualContextMenuInitialTranslation || + translation_type == TranslationType::kManualContextMenuReTranslation) + return TranslationStatus::kRevertedManualContextMenuTranslation; if (translation_type == TranslationType::kAutomaticTranslationByPref || translation_type == TranslationType::kAutomaticTranslationByLink) return TranslationStatus::kRevertedAutomaticTranslation; @@ -543,12 +557,20 @@ TranslateMetricsLoggerImpl::ConvertTranslationTypeToFailedTranslationStatus( TranslationType translation_type, bool was_translation_error) { - if (translation_type == TranslationType::kManualInitialTranslation || - translation_type == TranslationType::kManualReTranslation) { + if (translation_type == TranslationType::kManualUiInitialTranslation || + translation_type == TranslationType::kManualUiReTranslation) { if (was_translation_error) - return TranslationStatus::kFailedWithErrorManualTranslation; + return TranslationStatus::kFailedWithErrorManualUiTranslation; else - return TranslationStatus::kFailedWithNoErrorManualTranslation; + return TranslationStatus::kFailedWithNoErrorManualUiTranslation; + } + if (translation_type == + TranslationType::kManualContextMenuInitialTranslation || + translation_type == TranslationType::kManualContextMenuReTranslation) { + if (was_translation_error) + return TranslationStatus::kFailedWithErrorManualContextMenuTranslation; + else + return TranslationStatus::kFailedWithNoErrorManualContextMenuTranslation; } if (translation_type == TranslationType::kAutomaticTranslationByPref || translation_type == TranslationType::kAutomaticTranslationByLink) { @@ -566,9 +588,13 @@ TranslationType translation_type) { if (is_translation_in_progress) return TranslationStatus::kTranslationAbandoned; - if (translation_type == TranslationType::kManualInitialTranslation || - translation_type == TranslationType::kManualReTranslation) - return TranslationStatus::kSuccessFromManualTranslation; + if (translation_type == TranslationType::kManualUiInitialTranslation || + translation_type == TranslationType::kManualUiReTranslation) + return TranslationStatus::kSuccessFromManualUiTranslation; + if (translation_type == + TranslationType::kManualContextMenuInitialTranslation || + translation_type == TranslationType::kManualContextMenuReTranslation) + return TranslationStatus::kSuccessFromManualContextMenuTranslation; if (translation_type == TranslationType::kAutomaticTranslationByPref) return TranslationStatus::kSuccessFromAutomaticTranslationByPref; if (translation_type == TranslationType::kAutomaticTranslationByLink)
diff --git a/components/translate/core/browser/translate_metrics_logger_impl.h b/components/translate/core/browser/translate_metrics_logger_impl.h index ceb1080..ba761d6 100644 --- a/components/translate/core/browser/translate_metrics_logger_impl.h +++ b/components/translate/core/browser/translate_metrics_logger_impl.h
@@ -92,7 +92,8 @@ void LogDetectionReliabilityScore( const float& model_detection_reliability_score) override {} void LogUIInteraction(UIInteraction ui_interaction) override {} - TranslationType GetNextManualTranslationType() override; + TranslationType GetNextManualTranslationType( + bool is_context_menu_initiated_translation) override; void SetHasHrefTranslateTarget(bool has_href_translate_target) override {} void LogWasContentEmpty(bool was_content_empty) override {} }; @@ -153,7 +154,8 @@ void LogDetectionReliabilityScore( const float& model_detection_reliability_score) override; void LogUIInteraction(UIInteraction ui_interaction) override; - TranslationType GetNextManualTranslationType() override; + TranslationType GetNextManualTranslationType( + bool is_context_menu_initiated_translation) override; void SetHasHrefTranslateTarget(bool has_href_translate_target) override; void LogWasContentEmpty(bool was_content_empty) override;
diff --git a/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc b/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc index d27f861..6f615c7 100644 --- a/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc +++ b/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
@@ -75,6 +75,113 @@ return test_ukm_recorder_.get(); } + void SimulateAndCheckManualTranslation( + bool is_context_menu_initiated_translation, + bool was_translation_successful, + TranslateErrors::Type translate_error_type, + TranslationType expected_translation_type, + TranslationStatus expected_translation_status) { + if (was_translation_successful) + EXPECT_EQ(translate_error_type, TranslateErrors::NONE); + + translate_metrics_logger()->LogInitialState(); + + translate_metrics_logger()->LogTranslationStarted( + translate_metrics_logger()->GetNextManualTranslationType( + is_context_menu_initiated_translation)); + translate_metrics_logger()->LogTranslationFinished( + was_translation_successful, translate_error_type); + + translate_metrics_logger()->RecordMetrics(true); + + histogram_tester()->ExpectUniqueSample(kTranslateTranslationType, + expected_translation_type, 1); + histogram_tester()->ExpectUniqueSample(kTranslateTranslationStatus, + expected_translation_status, 1); + + CheckTranslateStateHistograms( + TranslateState::kNotTranslatedNoUI, + (was_translation_successful ? TranslateState::kTranslatedNoUI + : TranslateState::kNotTranslatedNoUI), + (was_translation_successful ? 1 : 0), 0); + CheckTranslateErrors( + translate_error_type, + (translate_error_type != TranslateErrors::NONE ? 1 : 0)); + } + + void SimulateAndCheckRepeatedManualTranslationsAndReversions( + int num_translations_and_reversions, + bool is_context_menu_initiated_translation, + TranslationType expected_initial_translation_type, + TranslationType expected_re_translation_type, + TranslationStatus expected_translation_status) { + translate_metrics_logger()->LogInitialState(); + for (int i = 0; i < num_translations_and_reversions; i++) { + translate_metrics_logger()->LogTranslationStarted( + translate_metrics_logger()->GetNextManualTranslationType( + is_context_menu_initiated_translation)); + translate_metrics_logger()->LogTranslationFinished(true, + TranslateErrors::NONE); + translate_metrics_logger()->LogReversion(); + } + + translate_metrics_logger()->RecordMetrics(true); + + histogram_tester()->ExpectTotalCount(kTranslateTranslationType, + num_translations_and_reversions); + histogram_tester()->ExpectBucketCount(kTranslateTranslationType, + expected_initial_translation_type, 1); + histogram_tester()->ExpectBucketCount(kTranslateTranslationType, + expected_re_translation_type, + num_translations_and_reversions - 1); + histogram_tester()->ExpectUniqueSample(kTranslateTranslationStatus, + expected_translation_status, + num_translations_and_reversions); + + CheckTranslateStateHistograms( + TranslateState::kNotTranslatedNoUI, TranslateState::kNotTranslatedNoUI, + num_translations_and_reversions, num_translations_and_reversions); + CheckTranslateErrors(TranslateErrors::NONE, 0); + } + + void SimulateAndCheckAutomaticThenManualTranslation( + bool is_context_menu_initiated_translation, + TranslationType expected_manual_translation_type, + TranslationStatus expected_manual_translation_status) { + translate_metrics_logger()->LogTranslationStarted( + TranslationType::kAutomaticTranslationByPref); + translate_metrics_logger()->LogInitialState(); + translate_metrics_logger()->LogTranslationFinished(true, + TranslateErrors::NONE); + + translate_metrics_logger()->LogReversion(); + + translate_metrics_logger()->LogTranslationStarted( + translate_metrics_logger()->GetNextManualTranslationType( + is_context_menu_initiated_translation)); + translate_metrics_logger()->LogTranslationFinished(true, + TranslateErrors::NONE); + + translate_metrics_logger()->RecordMetrics(true); + + histogram_tester()->ExpectTotalCount(kTranslateTranslationType, 2); + histogram_tester()->ExpectBucketCount( + kTranslateTranslationType, TranslationType::kAutomaticTranslationByPref, + 1); + histogram_tester()->ExpectBucketCount(kTranslateTranslationType, + expected_manual_translation_type, 1); + histogram_tester()->ExpectTotalCount(kTranslateTranslationStatus, 2); + histogram_tester()->ExpectBucketCount( + kTranslateTranslationStatus, + TranslationStatus::kRevertedAutomaticTranslation, 1); + histogram_tester()->ExpectBucketCount( + kTranslateTranslationStatus, expected_manual_translation_status, 1); + + CheckTranslateStateHistograms(TranslateState::kTranslatedNoUI, + TranslateState::kTranslatedNoUI, 2, 1); + CheckTranslateErrors(TranslateErrors::NONE, 0); + } + void CheckTranslateStateHistograms(TranslateState expected_initial_state, TranslateState expected_final_state, int expected_num_translations, @@ -413,7 +520,8 @@ translate_metrics_logger()->LogUIInteraction(UIInteraction::kTranslate); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); test_clock.Advance(translation_delay1); translate_metrics_logger()->LogTranslationFinished(true, TranslateErrors::NONE); @@ -441,7 +549,8 @@ translate_metrics_logger()->LogUIInteraction(UIInteraction::kTranslate); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); test_clock.Advance(translation_delay2); translate_metrics_logger()->LogTranslationFinished(false, TranslateErrors::NETWORK); @@ -451,7 +560,8 @@ translate_metrics_logger()->LogUIInteraction(UIInteraction::kTranslate); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); test_clock.Advance(translation_delay3); translate_metrics_logger()->LogTranslationFinished(true, TranslateErrors::NONE); @@ -524,7 +634,8 @@ translate_metrics_logger()->LogInitialState(); translate_metrics_logger()->LogUIChange(true); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); translate_metrics_logger()->LogTranslationFinished(true, TranslateErrors::NONE); translate_metrics_logger()->LogReversion(); @@ -692,79 +803,62 @@ } TEST_F(TranslateMetricsLoggerImplTest, LogTranslationAndReversion) { - // Simulate a page load where the user translates a page and it is successful. - translate_metrics_logger()->LogInitialState(); + // Simulate a page load where the user translates a page via the Translate UI + // and it is successful. + SimulateAndCheckManualTranslation( + false, true, TranslateErrors::NONE, + TranslationType::kManualUiInitialTranslation, + TranslationStatus::kSuccessFromManualUiTranslation); - translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); - translate_metrics_logger()->LogTranslationFinished(true, - TranslateErrors::NONE); - - translate_metrics_logger()->RecordMetrics(true); - - histogram_tester()->ExpectUniqueSample( - kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1); - histogram_tester()->ExpectUniqueSample( - kTranslateTranslationStatus, - TranslationStatus::kSuccessFromManualTranslation, 1); - - CheckTranslateStateHistograms(TranslateState::kNotTranslatedNoUI, - TranslateState::kTranslatedNoUI, 1, 0); - CheckTranslateErrors(TranslateErrors::NONE, 0); - - // Simulate a failed translation with an error. + // Simulate a page load where the user translates a page via the Context Menu + // and it is successful. ResetTest(); - translate_metrics_logger()->LogInitialState(); + SimulateAndCheckManualTranslation( + true, true, TranslateErrors::NONE, + TranslationType::kManualContextMenuInitialTranslation, + TranslationStatus::kSuccessFromManualContextMenuTranslation); - translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); - translate_metrics_logger()->LogTranslationFinished(false, - TranslateErrors::NETWORK); - - translate_metrics_logger()->RecordMetrics(true); - - histogram_tester()->ExpectUniqueSample( - kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1); - histogram_tester()->ExpectUniqueSample( - kTranslateTranslationStatus, - TranslationStatus::kFailedWithErrorManualTranslation, 1); - - CheckTranslateStateHistograms(TranslateState::kNotTranslatedNoUI, - TranslateState::kNotTranslatedNoUI, 0, 0); - CheckTranslateErrors(TranslateErrors::NETWORK, 1); - - // Simulate a failed translation without an error. + // Simulate a failed manual translation via the Translate UI with an error. ResetTest(); - translate_metrics_logger()->LogInitialState(); + SimulateAndCheckManualTranslation( + false, false, TranslateErrors::NETWORK, + TranslationType::kManualUiInitialTranslation, + TranslationStatus::kFailedWithErrorManualUiTranslation); - translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); - translate_metrics_logger()->LogTranslationFinished(false, - TranslateErrors::NONE); + // Simulate a failed manual translation via the Context Menu with an error. + ResetTest(); + SimulateAndCheckManualTranslation( + true, false, TranslateErrors::NETWORK, + TranslationType::kManualContextMenuInitialTranslation, + TranslationStatus::kFailedWithErrorManualContextMenuTranslation); - translate_metrics_logger()->RecordMetrics(true); + // Simulate a failed manualtranslation via the Translate UI without an error. + ResetTest(); + SimulateAndCheckManualTranslation( + false, false, TranslateErrors::NONE, + TranslationType::kManualUiInitialTranslation, + TranslationStatus::kFailedWithNoErrorManualUiTranslation); - histogram_tester()->ExpectUniqueSample( - kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1); - histogram_tester()->ExpectUniqueSample( - kTranslateTranslationStatus, - TranslationStatus::kFailedWithNoErrorManualTranslation, 1); - - CheckTranslateStateHistograms(TranslateState::kNotTranslatedNoUI, - TranslateState::kNotTranslatedNoUI, 0, 0); - CheckTranslateErrors(TranslateErrors::NONE, 0); + // Simulate a failed manualtranslation via the Context Menu without an error. + ResetTest(); + SimulateAndCheckManualTranslation( + true, false, TranslateErrors::NONE, + TranslationType::kManualContextMenuInitialTranslation, + TranslationStatus::kFailedWithNoErrorManualContextMenuTranslation); // Simulate a translation that does not finish. ResetTest(); translate_metrics_logger()->LogInitialState(); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); translate_metrics_logger()->RecordMetrics(true); histogram_tester()->ExpectUniqueSample( - kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1); + kTranslateTranslationType, TranslationType::kManualUiInitialTranslation, + 1); histogram_tester()->ExpectUniqueSample( kTranslateTranslationStatus, TranslationStatus::kTranslationAbandoned, 1); @@ -778,11 +872,13 @@ translate_metrics_logger()->LogInitialState(); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); translate_metrics_logger()->LogTranslationFinished(true, TranslateErrors::NONE); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); translate_metrics_logger()->LogTranslationFinished(false, TranslateErrors::NETWORK); @@ -790,15 +886,16 @@ histogram_tester()->ExpectTotalCount(kTranslateTranslationType, 2); histogram_tester()->ExpectBucketCount( - kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1); + kTranslateTranslationType, TranslationType::kManualUiInitialTranslation, + 1); histogram_tester()->ExpectBucketCount( - kTranslateTranslationType, TranslationType::kManualReTranslation, 1); + kTranslateTranslationType, TranslationType::kManualUiReTranslation, 1); histogram_tester()->ExpectTotalCount(kTranslateTranslationStatus, 2); histogram_tester()->ExpectBucketCount(kTranslateTranslationStatus, TranslationStatus::kNewTranslation, 1); histogram_tester()->ExpectBucketCount( kTranslateTranslationStatus, - TranslationStatus::kFailedWithErrorManualTranslation, 1); + TranslationStatus::kFailedWithErrorManualUiTranslation, 1); CheckTranslateStateHistograms(TranslateState::kNotTranslatedNoUI, TranslateState::kTranslatedNoUI, 1, 0); @@ -810,9 +907,11 @@ translate_metrics_logger()->LogInitialState(); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); translate_metrics_logger()->LogTranslationFinished(true, TranslateErrors::NONE); @@ -820,13 +919,14 @@ histogram_tester()->ExpectTotalCount(kTranslateTranslationType, 2); histogram_tester()->ExpectBucketCount( - kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1); + kTranslateTranslationType, TranslationType::kManualUiInitialTranslation, + 1); histogram_tester()->ExpectBucketCount( - kTranslateTranslationType, TranslationType::kManualReTranslation, 1); + kTranslateTranslationType, TranslationType::kManualUiReTranslation, 1); histogram_tester()->ExpectTotalCount(kTranslateTranslationStatus, 2); histogram_tester()->ExpectBucketCount( kTranslateTranslationStatus, - TranslationStatus::kSuccessFromManualTranslation, 1); + TranslationStatus::kSuccessFromManualUiTranslation, 1); histogram_tester()->ExpectBucketCount(kTranslateTranslationStatus, TranslationStatus::kNewTranslation, 1); @@ -940,39 +1040,21 @@ TranslateState::kNotTranslatedNoUI, 0, 0); CheckTranslateErrors(TranslateErrors::NONE, 0); - // Simulate a page that is repeatedly translated and then reverted. + // Simulate a page that is repeatedly manually translated via the Translate UI + // and then reverted. ResetTest(); + SimulateAndCheckRepeatedManualTranslationsAndReversions( + 100, false, TranslationType::kManualUiInitialTranslation, + TranslationType::kManualUiReTranslation, + TranslationStatus::kRevertedManualUiTranslation); - int num_translations_and_reversions = 100; - - translate_metrics_logger()->LogInitialState(); - - for (int i = 0; i < num_translations_and_reversions; i++) { - translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); - translate_metrics_logger()->LogTranslationFinished(true, - TranslateErrors::NONE); - translate_metrics_logger()->LogReversion(); - } - - translate_metrics_logger()->RecordMetrics(true); - - histogram_tester()->ExpectTotalCount(kTranslateTranslationType, - num_translations_and_reversions); - histogram_tester()->ExpectBucketCount( - kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1); - histogram_tester()->ExpectBucketCount(kTranslateTranslationType, - TranslationType::kManualReTranslation, - num_translations_and_reversions - 1); - histogram_tester()->ExpectUniqueSample( - kTranslateTranslationStatus, - TranslationStatus::kRevertedManualTranslation, - num_translations_and_reversions); - - CheckTranslateStateHistograms( - TranslateState::kNotTranslatedNoUI, TranslateState::kNotTranslatedNoUI, - num_translations_and_reversions, num_translations_and_reversions); - CheckTranslateErrors(TranslateErrors::NONE, 0); + // Simulate a page that is repeatedly manually translated via the Context Menu + // and then reverted. + ResetTest(); + SimulateAndCheckRepeatedManualTranslationsAndReversions( + 100, true, TranslationType::kManualContextMenuInitialTranslation, + TranslationType::kManualContextMenuReTranslation, + TranslationStatus::kRevertedManualContextMenuTranslation); // Simulates a page that is automatically translated by pref then reverted. ResetTest(); @@ -998,40 +1080,18 @@ CheckTranslateErrors(TranslateErrors::NONE, 0); // Simulates a page that is automatically translated, then reverted, and - // finally manually translated. + // finally manually translated via either the Translate UI. ResetTest(); - translate_metrics_logger()->LogTranslationStarted( - TranslationType::kAutomaticTranslationByPref); - translate_metrics_logger()->LogInitialState(); - translate_metrics_logger()->LogTranslationFinished(true, - TranslateErrors::NONE); + SimulateAndCheckAutomaticThenManualTranslation( + false, TranslationType::kManualUiReTranslation, + TranslationStatus::kSuccessFromManualUiTranslation); - translate_metrics_logger()->LogReversion(); - - translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); - translate_metrics_logger()->LogTranslationFinished(true, - TranslateErrors::NONE); - - translate_metrics_logger()->RecordMetrics(true); - - histogram_tester()->ExpectTotalCount(kTranslateTranslationType, 2); - histogram_tester()->ExpectBucketCount( - kTranslateTranslationType, TranslationType::kAutomaticTranslationByPref, - 1); - histogram_tester()->ExpectBucketCount( - kTranslateTranslationType, TranslationType::kManualReTranslation, 1); - histogram_tester()->ExpectTotalCount(kTranslateTranslationStatus, 2); - histogram_tester()->ExpectBucketCount( - kTranslateTranslationStatus, - TranslationStatus::kRevertedAutomaticTranslation, 1); - histogram_tester()->ExpectBucketCount( - kTranslateTranslationStatus, - TranslationStatus::kSuccessFromManualTranslation, 1); - - CheckTranslateStateHistograms(TranslateState::kTranslatedNoUI, - TranslateState::kTranslatedNoUI, 2, 1); - CheckTranslateErrors(TranslateErrors::NONE, 0); + // Simulates a page that is automatically translated, then reverted, and + // finally manually translated via either the Context Menu + ResetTest(); + SimulateAndCheckAutomaticThenManualTranslation( + true, TranslationType::kManualContextMenuReTranslation, + TranslationStatus::kSuccessFromManualContextMenuTranslation); } TEST_F(TranslateMetricsLoggerImplTest, LogTranslationLanguages) { @@ -1057,7 +1117,8 @@ for (int i = 0; i < test.num_translations; ++i) { translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); translate_metrics_logger()->LogTranslationFinished(true, TranslateErrors::NONE); } @@ -1119,7 +1180,8 @@ // Simulates the translations with the predefined errors. for (const auto& test : kTests) { translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); translate_metrics_logger()->LogTranslationFinished( test.was_translation_successful, test.error_type); } @@ -1128,22 +1190,23 @@ histogram_tester()->ExpectTotalCount(kTranslateTranslationType, 12); histogram_tester()->ExpectBucketCount( - kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1); + kTranslateTranslationType, TranslationType::kManualUiInitialTranslation, + 1); histogram_tester()->ExpectBucketCount( - kTranslateTranslationType, TranslationType::kManualReTranslation, 11); + kTranslateTranslationType, TranslationType::kManualUiReTranslation, 11); histogram_tester()->ExpectTotalCount(kTranslateTranslationStatus, 12); histogram_tester()->ExpectBucketCount( kTranslateTranslationStatus, - TranslationStatus::kSuccessFromManualTranslation, 1); + TranslationStatus::kSuccessFromManualUiTranslation, 1); histogram_tester()->ExpectBucketCount(kTranslateTranslationStatus, TranslationStatus::kNewTranslation, 3); histogram_tester()->ExpectBucketCount( kTranslateTranslationStatus, - TranslationStatus::kFailedWithNoErrorManualTranslation, 2); + TranslationStatus::kFailedWithNoErrorManualUiTranslation, 2); histogram_tester()->ExpectBucketCount( kTranslateTranslationStatus, - TranslationStatus::kFailedWithErrorManualTranslation, 6); + TranslationStatus::kFailedWithErrorManualUiTranslation, 6); // We expect to capture the first non-NONE value, and the total number of // non-NONE errors. @@ -1163,7 +1226,8 @@ translate_metrics_logger()->LogInitialState(); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); translate_metrics_logger()->LogTranslationFinished(true, TranslateErrors::NONE); translate_metrics_logger()->LogUIChange(true); @@ -1219,7 +1283,8 @@ // Translate the page (while still in the background). translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); translate_metrics_logger()->LogTranslationFinished(true, TranslateErrors::NONE); @@ -1256,7 +1321,8 @@ // Translation starts, but takes a while. We should count this time while the // translation is in progress as "not translated". translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); test_clock.Advance(delay2); @@ -1352,7 +1418,8 @@ translate_metrics_logger()->SetInternalClockForTesting(&test_clock); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); test_clock.Advance(default_delay); translate_metrics_logger()->LogTranslationFinished(true, TranslateErrors::NONE); @@ -1367,7 +1434,8 @@ translate_metrics_logger()->SetInternalClockForTesting(&test_clock); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); test_clock.Advance(default_delay); translate_metrics_logger()->LogTranslationFinished(false, TranslateErrors::NETWORK); @@ -1382,7 +1450,8 @@ translate_metrics_logger()->SetInternalClockForTesting(&test_clock); translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); test_clock.Advance(default_delay); translate_metrics_logger()->RecordMetrics(true); @@ -1404,7 +1473,8 @@ for (const auto& test : kTests) { translate_metrics_logger()->LogTranslationStarted( - translate_metrics_logger()->GetNextManualTranslationType()); + translate_metrics_logger()->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); test_clock.Advance(test.time_to_translate); translate_metrics_logger()->LogTranslationFinished( test.translate_error_type == TranslateErrors::NONE,
diff --git a/components/translate/core/browser/translate_ui_delegate.cc b/components/translate/core/browser/translate_ui_delegate.cc index 4a11200..acef6acc 100644 --- a/components/translate/core/browser/translate_ui_delegate.cc +++ b/components/translate/core/browser/translate_ui_delegate.cc
@@ -321,7 +321,8 @@ translate_manager_->TranslatePage( GetSourceLanguageCode(), GetTargetLanguageCode(), false, translate_manager_->GetActiveTranslateMetricsLogger() - ->GetNextManualTranslationType()); + ->GetNextManualTranslationType( + /*is_context_menu_initiated_translation=*/false)); UMA_HISTOGRAM_BOOLEAN(kPerformTranslate, true); if (IsLikelyAmpCacheUrl(translate_driver_->GetLastCommittedURL())) UMA_HISTOGRAM_BOOLEAN(kPerformTranslateAmpCacheUrl, true);
diff --git a/components/zucchini/disassembler_elf.cc b/components/zucchini/disassembler_elf.cc index 07726a86..9c9ebb8 100644 --- a/components/zucchini/disassembler_elf.cc +++ b/components/zucchini/disassembler_elf.cc
@@ -43,8 +43,8 @@ // Decides how a section affects ELF parsing, and returns a bit field composed // from SectionJudgement values. -template <class Traits> -int JudgeSection(size_t image_size, const typename Traits::Elf_Shdr* section) { +template <class TRAITS> +int JudgeSection(size_t image_size, const typename TRAITS::Elf_Shdr* section) { // BufferRegion uses |size_t| this can be 32-bit in some cases. For Elf64 // |sh_addr|, |sh_offset| and |sh_size| are 64-bit this can result in // overflows in the subsequent validation steps. @@ -96,21 +96,21 @@ } // Determines whether |section| is a reloc section. -template <class Traits> -bool IsRelocSection(const typename Traits::Elf_Shdr& section) { +template <class TRAITS> +bool IsRelocSection(const typename TRAITS::Elf_Shdr& section) { DCHECK_GT(section.sh_size, 0U); if (section.sh_type == elf::SHT_REL) { // Also validate |section.sh_entsize|, which gets used later. - return section.sh_entsize == sizeof(typename Traits::Elf_Rel); + return section.sh_entsize == sizeof(typename TRAITS::Elf_Rel); } if (section.sh_type == elf::SHT_RELA) - return section.sh_entsize == sizeof(typename Traits::Elf_Rela); + return section.sh_entsize == sizeof(typename TRAITS::Elf_Rela); return false; } // Determines whether |section| is a section with executable code. -template <class Traits> -bool IsExecSection(const typename Traits::Elf_Shdr& section) { +template <class TRAITS> +bool IsExecSection(const typename TRAITS::Elf_Shdr& section) { DCHECK_GT(section.sh_size, 0U); return section.sh_type == elf::SHT_PROGBITS && (section.sh_flags & elf::SHF_EXECINSTR) != 0; @@ -149,8 +149,8 @@ /******** DisassemblerElf ********/ // static. -template <class Traits> -bool DisassemblerElf<Traits>::QuickDetect(ConstBufferView image) { +template <class TRAITS> +bool DisassemblerElf<TRAITS>::QuickDetect(ConstBufferView image) { BufferSource source(image); // Do not consume the bytes for the magic value, as they are part of the @@ -183,25 +183,25 @@ return true; } -template <class Traits> -DisassemblerElf<Traits>::~DisassemblerElf() = default; +template <class TRAITS> +DisassemblerElf<TRAITS>::~DisassemblerElf() = default; -template <class Traits> -ExecutableType DisassemblerElf<Traits>::GetExeType() const { +template <class TRAITS> +ExecutableType DisassemblerElf<TRAITS>::GetExeType() const { return Traits::kExeType; } -template <class Traits> -std::string DisassemblerElf<Traits>::GetExeTypeString() const { +template <class TRAITS> +std::string DisassemblerElf<TRAITS>::GetExeTypeString() const { return Traits::kExeTypeString; } // |num_equivalence_iterations_| = 2 for reloc -> abs32. -template <class Traits> -DisassemblerElf<Traits>::DisassemblerElf() : Disassembler(2) {} +template <class TRAITS> +DisassemblerElf<TRAITS>::DisassemblerElf() : Disassembler(2) {} -template <class Traits> -bool DisassemblerElf<Traits>::Parse(ConstBufferView image) { +template <class TRAITS> +bool DisassemblerElf<TRAITS>::Parse(ConstBufferView image) { image_ = image; if (!ParseHeader()) return false; @@ -209,8 +209,8 @@ return true; } -template <class Traits> -std::unique_ptr<ReferenceReader> DisassemblerElf<Traits>::MakeReadRelocs( +template <class TRAITS> +std::unique_ptr<ReferenceReader> DisassemblerElf<TRAITS>::MakeReadRelocs( offset_t lo, offset_t hi) { DCHECK_LE(lo, hi); @@ -224,14 +224,14 @@ supported_relocation_type(), lo, hi, translator_); } -template <class Traits> -std::unique_ptr<ReferenceWriter> DisassemblerElf<Traits>::MakeWriteRelocs( +template <class TRAITS> +std::unique_ptr<ReferenceWriter> DisassemblerElf<TRAITS>::MakeWriteRelocs( MutableBufferView image) { return std::make_unique<RelocWriterElf>(image, Traits::kBitness, translator_); } -template <class Traits> -bool DisassemblerElf<Traits>::ParseHeader() { +template <class TRAITS> +bool DisassemblerElf<TRAITS>::ParseHeader() { BufferSource source(image_); // Ensure any offsets will fit within the |image_|'s bounds. if (!base::IsValueInRangeForNumericType<offset_t>(image_.size())) @@ -329,8 +329,8 @@ return true; } -template <class Traits> -void DisassemblerElf<Traits>::ExtractInterestingSectionHeaders() { +template <class TRAITS> +void DisassemblerElf<TRAITS>::ExtractInterestingSectionHeaders() { DCHECK(reloc_section_dims_.empty()); DCHECK(exec_headers_.empty()); for (elf::Elf32_Half i = 0; i < sections_count_; ++i) { @@ -350,8 +350,8 @@ std::sort(exec_headers_.begin(), exec_headers_.end(), comp); } -template <class Traits> -void DisassemblerElf<Traits>::GetAbs32FromRelocSections() { +template <class TRAITS> +void DisassemblerElf<TRAITS>::GetAbs32FromRelocSections() { constexpr int kAbs32Width = Traits::kVAWidth; DCHECK(abs32_locations_.empty()); @@ -380,15 +380,15 @@ abs32_locations_.shrink_to_fit(); } -template <class Traits> -void DisassemblerElf<Traits>::GetRel32FromCodeSections() { +template <class TRAITS> +void DisassemblerElf<TRAITS>::GetRel32FromCodeSections() { for (const typename Traits::Elf_Shdr* section : exec_headers_) ParseExecSection(*section); PostProcessRel32(); } -template <class Traits> -void DisassemblerElf<Traits>::ParseSections() { +template <class TRAITS> +void DisassemblerElf<TRAITS>::ParseSections() { ExtractInterestingSectionHeaders(); GetAbs32FromRelocSections(); GetRel32FromCodeSections(); @@ -396,32 +396,34 @@ /******** DisassemblerElfIntel ********/ -template <class Traits> -DisassemblerElfIntel<Traits>::DisassemblerElfIntel() = default; +template <class TRAITS> +DisassemblerElfIntel<TRAITS>::DisassemblerElfIntel() = default; -template <class Traits> -DisassemblerElfIntel<Traits>::~DisassemblerElfIntel() = default; +template <class TRAITS> +DisassemblerElfIntel<TRAITS>::~DisassemblerElfIntel() = default; -template <class Traits> -std::vector<ReferenceGroup> DisassemblerElfIntel<Traits>::MakeReferenceGroups() +template <class TRAITS> +std::vector<ReferenceGroup> DisassemblerElfIntel<TRAITS>::MakeReferenceGroups() const { return { - {ReferenceTypeTraits{sizeof(Traits::Elf_Rel::r_offset), TypeTag(kReloc), + {ReferenceTypeTraits{sizeof(TRAITS::Elf_Rel::r_offset), TypeTag(kReloc), PoolTag(kReloc)}, - &DisassemblerElfIntel<Traits>::MakeReadRelocs, - &DisassemblerElfIntel<Traits>::MakeWriteRelocs}, + &DisassemblerElfIntel<TRAITS>::MakeReadRelocs, + &DisassemblerElfIntel<TRAITS>::MakeWriteRelocs}, {ReferenceTypeTraits{Traits::kVAWidth, TypeTag(kAbs32), PoolTag(kAbs32)}, - &DisassemblerElfIntel<Traits>::MakeReadAbs32, - &DisassemblerElfIntel<Traits>::MakeWriteAbs32}, + &DisassemblerElfIntel<TRAITS>::MakeReadAbs32, + &DisassemblerElfIntel<TRAITS>::MakeWriteAbs32}, // N.B.: Rel32 |width| is 4 bytes, even for x64. {ReferenceTypeTraits{4, TypeTag(kRel32), PoolTag(kRel32)}, - &DisassemblerElfIntel<Traits>::MakeReadRel32, - &DisassemblerElfIntel<Traits>::MakeWriteRel32}}; + &DisassemblerElfIntel<TRAITS>::MakeReadRel32, + &DisassemblerElfIntel<TRAITS>::MakeWriteRel32}}; } -template <class Traits> -void DisassemblerElfIntel<Traits>::ParseExecSection( - const typename Traits::Elf_Shdr& section) { +template <class TRAITS> +void DisassemblerElfIntel<TRAITS>::ParseExecSection( + const typename TRAITS::Elf_Shdr& section) { + constexpr int kAbs32Width = Traits::kVAWidth; + // |this->| is needed to access protected members of templated base class. To // reduce noise, use local references for these. ConstBufferView& image_ = this->image_; @@ -435,8 +437,8 @@ AddressTranslator::RvaToOffsetCache target_rva_checker(translator_); ConstBufferView region(image_.begin() + section.sh_offset, section.sh_size); - Abs32GapFinder gap_finder(image_, region, abs32_locations_, 4); - typename Traits::Rel32FinderUse rel_finder(image_, translator_); + Abs32GapFinder gap_finder(image_, region, abs32_locations_, kAbs32Width); + typename TRAITS::Rel32FinderUse rel_finder(image_, translator_); // Iterate over gaps between abs32 references, to avoid collision. while (gap_finder.FindNext()) { rel_finder.SetRegion(gap_finder.GetGap()); @@ -452,43 +454,43 @@ } } -template <class Traits> -void DisassemblerElfIntel<Traits>::PostProcessRel32() { +template <class TRAITS> +void DisassemblerElfIntel<TRAITS>::PostProcessRel32() { rel32_locations_.shrink_to_fit(); std::sort(rel32_locations_.begin(), rel32_locations_.end()); } -template <class Traits> -std::unique_ptr<ReferenceReader> DisassemblerElfIntel<Traits>::MakeReadAbs32( +template <class TRAITS> +std::unique_ptr<ReferenceReader> DisassemblerElfIntel<TRAITS>::MakeReadAbs32( offset_t lo, offset_t hi) { // TODO(huangs): Don't use Abs32RvaExtractorWin32 here; use new class that // caters to different ELF architectures. Abs32RvaExtractorWin32 abs_rva_extractor( - this->image_, AbsoluteAddress(Traits::kBitness, kElfImageBase), + this->image_, AbsoluteAddress(TRAITS::kBitness, kElfImageBase), this->abs32_locations_, lo, hi); return std::make_unique<Abs32ReaderWin32>(std::move(abs_rva_extractor), this->translator_); } -template <class Traits> -std::unique_ptr<ReferenceWriter> DisassemblerElfIntel<Traits>::MakeWriteAbs32( +template <class TRAITS> +std::unique_ptr<ReferenceWriter> DisassemblerElfIntel<TRAITS>::MakeWriteAbs32( MutableBufferView image) { return std::make_unique<Abs32WriterWin32>( - image, AbsoluteAddress(Traits::kBitness, kElfImageBase), + image, AbsoluteAddress(TRAITS::kBitness, kElfImageBase), this->translator_); } -template <class Traits> -std::unique_ptr<ReferenceReader> DisassemblerElfIntel<Traits>::MakeReadRel32( +template <class TRAITS> +std::unique_ptr<ReferenceReader> DisassemblerElfIntel<TRAITS>::MakeReadRel32( offset_t lo, offset_t hi) { return std::make_unique<Rel32ReaderX86>(this->image_, lo, hi, &rel32_locations_, this->translator_); } -template <class Traits> -std::unique_ptr<ReferenceWriter> DisassemblerElfIntel<Traits>::MakeWriteRel32( +template <class TRAITS> +std::unique_ptr<ReferenceWriter> DisassemblerElfIntel<TRAITS>::MakeWriteRel32( MutableBufferView image) { return std::make_unique<Rel32WriterX86>(image, this->translator_); }
diff --git a/components/zucchini/disassembler_elf.h b/components/zucchini/disassembler_elf.h index 17e7523..ffd16907e 100644 --- a/components/zucchini/disassembler_elf.h +++ b/components/zucchini/disassembler_elf.h
@@ -65,9 +65,10 @@ }; // Disassembler for ELF. -template <class Traits> +template <class TRAITS> class DisassemblerElf : public Disassembler { public: + using Traits = TRAITS; // Applies quick checks to determine whether |image| *may* point to the start // of an executable. Returns true iff the check passes. static bool QuickDetect(ConstBufferView image); @@ -155,9 +156,10 @@ }; // Disassembler for ELF with Intel architectures. -template <class Traits> -class DisassemblerElfIntel : public DisassemblerElf<Traits> { +template <class TRAITS> +class DisassemblerElfIntel : public DisassemblerElf<TRAITS> { public: + using Traits = TRAITS; enum ReferenceType : uint8_t { kReloc, kAbs32, kRel32, kTypeCount }; DisassemblerElfIntel();
diff --git a/components/zucchini/disassembler_win32.cc b/components/zucchini/disassembler_win32.cc index a23201b..37e43e5 100644 --- a/components/zucchini/disassembler_win32.cc +++ b/components/zucchini/disassembler_win32.cc
@@ -24,7 +24,6 @@ // Decides whether |image| points to a Win32 PE file. If this is a possibility, // assigns |source| to enable further parsing, and returns true. Otherwise // leaves |source| at an undefined state and returns false. -template <class Traits> bool ReadWin32Header(ConstBufferView image, BufferSource* source) { *source = BufferSource(image); @@ -47,9 +46,9 @@ return true; } -template <class Traits> +template <class TRAITS> const pe::ImageDataDirectory* ReadDataDirectory( - const typename Traits::ImageOptionalHeader* optional_header, + const typename TRAITS::ImageOptionalHeader* optional_header, size_t index) { if (index >= optional_header->number_of_rva_and_sizes) return nullptr; @@ -57,7 +56,7 @@ } // Decides whether |section| (assumed value) is a section that contains code. -template <class Traits> +template <class TRAITS> bool IsWin32CodeSection(const pe::ImageSectionHeader& section) { return (section.characteristics & kCodeCharacteristics) == kCodeCharacteristics; @@ -82,31 +81,31 @@ /******** DisassemblerWin32 ********/ // static. -template <class Traits> -bool DisassemblerWin32<Traits>::QuickDetect(ConstBufferView image) { +template <class TRAITS> +bool DisassemblerWin32<TRAITS>::QuickDetect(ConstBufferView image) { BufferSource source; - return ReadWin32Header<Traits>(image, &source); + return ReadWin32Header(image, &source); } // |num_equivalence_iterations_| = 2 for reloc -> abs32. -template <class Traits> -DisassemblerWin32<Traits>::DisassemblerWin32() : Disassembler(2) {} +template <class TRAITS> +DisassemblerWin32<TRAITS>::DisassemblerWin32() : Disassembler(2) {} -template <class Traits> -DisassemblerWin32<Traits>::~DisassemblerWin32() = default; +template <class TRAITS> +DisassemblerWin32<TRAITS>::~DisassemblerWin32() = default; -template <class Traits> -ExecutableType DisassemblerWin32<Traits>::GetExeType() const { +template <class TRAITS> +ExecutableType DisassemblerWin32<TRAITS>::GetExeType() const { return Traits::kExeType; } -template <class Traits> -std::string DisassemblerWin32<Traits>::GetExeTypeString() const { +template <class TRAITS> +std::string DisassemblerWin32<TRAITS>::GetExeTypeString() const { return Traits::kExeTypeString; } -template <class Traits> -std::vector<ReferenceGroup> DisassemblerWin32<Traits>::MakeReferenceGroups() +template <class TRAITS> +std::vector<ReferenceGroup> DisassemblerWin32<TRAITS>::MakeReferenceGroups() const { return { {ReferenceTypeTraits{2, TypeTag(kReloc), PoolTag(kReloc)}, @@ -118,8 +117,8 @@ }; } -template <class Traits> -std::unique_ptr<ReferenceReader> DisassemblerWin32<Traits>::MakeReadRelocs( +template <class TRAITS> +std::unique_ptr<ReferenceReader> DisassemblerWin32<TRAITS>::MakeReadRelocs( offset_t lo, offset_t hi) { if (!ParseAndStoreRelocBlocks()) @@ -135,8 +134,8 @@ translator_); } -template <class Traits> -std::unique_ptr<ReferenceReader> DisassemblerWin32<Traits>::MakeReadAbs32( +template <class TRAITS> +std::unique_ptr<ReferenceReader> DisassemblerWin32<TRAITS>::MakeReadAbs32( offset_t lo, offset_t hi) { ParseAndStoreAbs32(); @@ -146,8 +145,8 @@ translator_); } -template <class Traits> -std::unique_ptr<ReferenceReader> DisassemblerWin32<Traits>::MakeReadRel32( +template <class TRAITS> +std::unique_ptr<ReferenceReader> DisassemblerWin32<TRAITS>::MakeReadRel32( offset_t lo, offset_t hi) { ParseAndStoreRel32(); @@ -155,8 +154,8 @@ translator_); } -template <class Traits> -std::unique_ptr<ReferenceWriter> DisassemblerWin32<Traits>::MakeWriteRelocs( +template <class TRAITS> +std::unique_ptr<ReferenceWriter> DisassemblerWin32<TRAITS>::MakeWriteRelocs( MutableBufferView image) { if (!ParseAndStoreRelocBlocks()) return std::make_unique<EmptyReferenceWriter>(); @@ -166,30 +165,30 @@ translator_); } -template <class Traits> -std::unique_ptr<ReferenceWriter> DisassemblerWin32<Traits>::MakeWriteAbs32( +template <class TRAITS> +std::unique_ptr<ReferenceWriter> DisassemblerWin32<TRAITS>::MakeWriteAbs32( MutableBufferView image) { return std::make_unique<Abs32WriterWin32>( image, AbsoluteAddress(Traits::kBitness, image_base_), translator_); } -template <class Traits> -std::unique_ptr<ReferenceWriter> DisassemblerWin32<Traits>::MakeWriteRel32( +template <class TRAITS> +std::unique_ptr<ReferenceWriter> DisassemblerWin32<TRAITS>::MakeWriteRel32( MutableBufferView image) { return std::make_unique<Rel32WriterX86>(image, translator_); } -template <class Traits> -bool DisassemblerWin32<Traits>::Parse(ConstBufferView image) { +template <class TRAITS> +bool DisassemblerWin32<TRAITS>::Parse(ConstBufferView image) { image_ = image; return ParseHeader(); } -template <class Traits> -bool DisassemblerWin32<Traits>::ParseHeader() { +template <class TRAITS> +bool DisassemblerWin32<TRAITS>::ParseHeader() { BufferSource source; - if (!ReadWin32Header<Traits>(image_, &source)) + if (!ReadWin32Header(image_, &source)) return false; constexpr size_t kDataDirBase = @@ -297,8 +296,8 @@ return true; } -template <class Traits> -bool DisassemblerWin32<Traits>::ParseAndStoreRelocBlocks() { +template <class TRAITS> +bool DisassemblerWin32<TRAITS>::ParseAndStoreRelocBlocks() { if (has_parsed_relocs_) return reloc_region_.lo() != kInvalidOffset; @@ -324,8 +323,8 @@ return true; } -template <class Traits> -bool DisassemblerWin32<Traits>::ParseAndStoreAbs32() { +template <class TRAITS> +bool DisassemblerWin32<TRAITS>::ParseAndStoreAbs32() { if (has_parsed_abs32_) return true; has_parsed_abs32_ = true; @@ -355,8 +354,8 @@ return true; } -template <class Traits> -bool DisassemblerWin32<Traits>::ParseAndStoreRel32() { +template <class TRAITS> +bool DisassemblerWin32<TRAITS>::ParseAndStoreRel32() { if (has_parsed_rel32_) return true; has_parsed_rel32_ = true;
diff --git a/components/zucchini/disassembler_win32.h b/components/zucchini/disassembler_win32.h index 8f9fd58..d952014 100644 --- a/components/zucchini/disassembler_win32.h +++ b/components/zucchini/disassembler_win32.h
@@ -52,9 +52,10 @@ using Address = uint64_t; }; -template <class Traits> +template <class TRAITS> class DisassemblerWin32 : public Disassembler { public: + using Traits = TRAITS; enum ReferenceType : uint8_t { kReloc, kAbs32, kRel32, kTypeCount }; // Applies quick checks to determine whether |image| *may* point to the start
diff --git a/components/zucchini/reloc_elf_unittest.cc b/components/zucchini/reloc_elf_unittest.cc index b03f403..8a1b932 100644 --- a/components/zucchini/reloc_elf_unittest.cc +++ b/components/zucchini/reloc_elf_unittest.cc
@@ -48,9 +48,10 @@ } // Helper to manipulate an image with one or more relocation tables. -template <class ElfIntelTraits> +template <class ELF_INTEL_TRAITS> class FakeImageWithReloc { public: + using ElfIntelTraits = ELF_INTEL_TRAITS; struct RelocSpec { offset_t start; std::vector<uint8_t> data;
diff --git a/content/browser/background_fetch/background_fetch_context.cc b/content/browser/background_fetch/background_fetch_context.cc index 84c8485..0521002d 100644 --- a/content/browser/background_fetch/background_fetch_context.cc +++ b/content/browser/background_fetch/background_fetch_context.cc
@@ -36,17 +36,13 @@ const scoped_refptr<ServiceWorkerContextWrapper>& service_worker_context, scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context) - : base::RefCountedDeleteOnSequence<BackgroundFetchContext>( - BrowserThread::GetTaskRunnerForThread( - ServiceWorkerContext::GetCoreThreadId())), - service_worker_context_(service_worker_context), + : service_worker_context_(service_worker_context), devtools_context_(std::move(devtools_context)), registration_notifier_( std::make_unique<BackgroundFetchRegistrationNotifier>()), delegate_proxy_(storage_partition) { - // Although this lives only on the service worker core thread, it is - // constructed on UI thread. DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(service_worker_context_); data_manager_ = std::make_unique<BackgroundFetchDataManager>( @@ -58,18 +54,18 @@ } BackgroundFetchContext::~BackgroundFetchContext() { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); service_worker_context_->RemoveObserver(scheduler_.get()); data_manager_->RemoveObserver(scheduler_.get()); } -void BackgroundFetchContext::InitializeOnCoreThread() { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); +void BackgroundFetchContext::Initialize() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); service_worker_context_->AddObserver(scheduler_.get()); data_manager_->AddObserver(scheduler_.get()); - data_manager_->InitializeOnCoreThread(); + data_manager_->Initialize(); data_manager_->GetInitializationData( base::BindOnce(&BackgroundFetchContext::DidGetInitializationData, weak_factory_.GetWeakPtr())); @@ -79,6 +75,8 @@ blink::mojom::BackgroundFetchError error, std::vector<background_fetch::BackgroundFetchInitializationData> initialization_data) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (error != blink::mojom::BackgroundFetchError::NONE) return; @@ -99,7 +97,7 @@ const blink::StorageKey& storage_key, const std::string& developer_id, blink::mojom::BackgroundFetchService::GetRegistrationCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); data_manager_->GetRegistration( service_worker_registration_id, storage_key, developer_id, @@ -111,7 +109,7 @@ int64_t service_worker_registration_id, const blink::StorageKey& storage_key, blink::mojom::BackgroundFetchService::GetDeveloperIdsCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); data_manager_->GetDeveloperIdsForServiceWorker( service_worker_registration_id, storage_key, std::move(callback)); @@ -122,7 +120,7 @@ blink::mojom::BackgroundFetchError error, BackgroundFetchRegistrationId registration_id, blink::mojom::BackgroundFetchRegistrationDataPtr registration_data) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (error != blink::mojom::BackgroundFetchError::NONE) { std::move(callback).Run( @@ -149,7 +147,7 @@ int render_frame_tree_node_id, const WebContents::Getter& wc_getter, blink::mojom::BackgroundFetchService::FetchCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // |registration_id| should be unique even if developer id has been // duplicated, because the caller of this function generates a new unique_id @@ -174,14 +172,11 @@ blink::mojom::BackgroundFetchUkmDataPtr ukm_data, int frame_tree_node_id, BackgroundFetchPermission permission) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - RunOrPostTaskOnThread( - FROM_HERE, BrowserThread::UI, - base::BindOnce(&background_fetch::RecordBackgroundFetchUkmEvent, - registration_id.storage_key(), requests.size(), - options.Clone(), icon, std::move(ukm_data), - frame_tree_node_id, permission)); + background_fetch::RecordBackgroundFetchUkmEvent( + registration_id.storage_key(), requests.size(), options.Clone(), icon, + std::move(ukm_data), frame_tree_node_id, permission); if (permission != BackgroundFetchPermission::BLOCKED) { data_manager_->CreateRegistration( @@ -200,7 +195,7 @@ void BackgroundFetchContext::GetIconDisplaySize( blink::mojom::BackgroundFetchService::GetIconDisplaySizeCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); delegate_proxy_.GetIconDisplaySize(std::move(callback)); } @@ -208,7 +203,7 @@ const BackgroundFetchRegistrationId& registration_id, blink::mojom::BackgroundFetchError error, blink::mojom::BackgroundFetchRegistrationDataPtr registration_data) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); auto iter = fetch_callbacks_.find(registration_id); @@ -234,6 +229,8 @@ const std::string& unique_id, mojo::PendingRemote<blink::mojom::BackgroundFetchRegistrationObserver> observer) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + registration_notifier_->AddObserver(unique_id, std::move(observer)); } @@ -243,7 +240,7 @@ const absl::optional<SkBitmap>& icon, blink::mojom::BackgroundFetchRegistrationService::UpdateUICallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); delegate_proxy_.UpdateUI(registration_id.unique_id(), title, icon, std::move(callback)); @@ -257,7 +254,8 @@ void BackgroundFetchContext::Abort( const BackgroundFetchRegistrationId& registration_id, blink::mojom::BackgroundFetchRegistrationService::AbortCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + scheduler_->Abort(registration_id, FailureReason::CANCELLED_BY_DEVELOPER, std::move(callback)); } @@ -267,7 +265,7 @@ std::unique_ptr<BackgroundFetchRequestMatchParams> match_params, blink::mojom::BackgroundFetchRegistrationService::MatchRequestsCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); data_manager_->MatchRequests( registration_id, std::move(match_params), @@ -282,7 +280,7 @@ callback, blink::mojom::BackgroundFetchError error, std::vector<blink::mojom::BackgroundFetchSettledFetchPtr> settled_fetches) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (error != blink::mojom::BackgroundFetchError::NONE) DCHECK(settled_fetches.empty()); @@ -297,21 +295,13 @@ } void BackgroundFetchContext::Shutdown() { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(&BackgroundFetchContext::ShutdownOnCoreThread, this)); -} - -void BackgroundFetchContext::ShutdownOnCoreThread() { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); - - data_manager_->ShutdownOnCoreThread(); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + data_manager_->Shutdown(); } void BackgroundFetchContext::SetDataManagerForTesting( std::unique_ptr<BackgroundFetchDataManager> data_manager) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(data_manager); data_manager_ = std::move(data_manager); scheduler_ = std::make_unique<BackgroundFetchScheduler>(
diff --git a/content/browser/background_fetch/background_fetch_context.h b/content/browser/background_fetch/background_fetch_context.h index 0b7fe97..3b58bdbc 100644 --- a/content/browser/background_fetch/background_fetch_context.h +++ b/content/browser/background_fetch/background_fetch_context.h
@@ -13,6 +13,7 @@ #include "base/macros.h" #include "base/memory/ref_counted_delete_on_sequence.h" #include "base/memory/weak_ptr.h" +#include "base/sequence_checker.h" #include "content/browser/background_fetch/background_fetch_delegate_proxy.h" #include "content/browser/background_fetch/background_fetch_event_dispatcher.h" #include "content/browser/background_fetch/storage/get_initialization_data_task.h" @@ -42,11 +43,9 @@ // Background Fetch requests function similarly to normal fetches except that // they are persistent across Chromium or service worker shutdown. // -// Deleted on the service worker core thread. -// TODO(crbug.com/824858): Make this single-threaded after the service worker -// core thread moves to the UI thread. +// Lives on the UI thread. class CONTENT_EXPORT BackgroundFetchContext - : public base::RefCountedDeleteOnSequence<BackgroundFetchContext> { + : public base::RefCounted<BackgroundFetchContext> { public: // The BackgroundFetchContext will watch the ServiceWorkerContextWrapper so // that it can respond to service worker events such as unregister. @@ -56,7 +55,7 @@ scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context); - void InitializeOnCoreThread(); + void Initialize(); // Called by the StoragePartitionImpl destructor. void Shutdown(); @@ -142,13 +141,10 @@ JobsInitializedOnBrowserRestart); friend class BackgroundFetchServiceTest; friend class BackgroundFetchJobControllerTest; - friend class base::DeleteHelper<BackgroundFetchContext>; - friend class base::RefCountedDeleteOnSequence<BackgroundFetchContext>; + friend class base::RefCounted<BackgroundFetchContext>; ~BackgroundFetchContext(); - void ShutdownOnCoreThread(); - // Called when an existing registration has been retrieved from the data // manager. If the registration does not exist then |registration| is nullptr. void DidGetRegistration( @@ -209,6 +205,8 @@ blink::mojom::BackgroundFetchService::FetchCallback> fetch_callbacks_; + SEQUENCE_CHECKER(sequence_checker_); + base::WeakPtrFactory<BackgroundFetchContext> weak_factory_{ this}; // Must be last.
diff --git a/content/browser/background_fetch/background_fetch_data_manager.cc b/content/browser/background_fetch/background_fetch_data_manager.cc index d47e90c1..bf5e3fc 100644 --- a/content/browser/background_fetch/background_fetch_data_manager.cc +++ b/content/browser/background_fetch/background_fetch_data_manager.cc
@@ -44,7 +44,7 @@ : service_worker_context_(std::move(service_worker_context)), storage_partition_(std::move(storage_partition)), quota_manager_proxy_(std::move(quota_manager_proxy)) { - // Constructed on the UI thread, then used on the service worker core thread. + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(storage_partition_); @@ -54,8 +54,8 @@ DCHECK(blob_storage_context_); } -void BackgroundFetchDataManager::InitializeOnCoreThread() { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); +void BackgroundFetchDataManager::Initialize() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Delete inactive registrations still in the DB. Cleanup(); @@ -63,17 +63,18 @@ void BackgroundFetchDataManager::AddObserver( BackgroundFetchDataManagerObserver* observer) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); observers_.AddObserver(observer); } void BackgroundFetchDataManager::RemoveObserver( BackgroundFetchDataManagerObserver* observer) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); observers_.RemoveObserver(observer); } void BackgroundFetchDataManager::Cleanup() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask(std::make_unique<background_fetch::CleanupTask>(this)); } @@ -81,6 +82,8 @@ BackgroundFetchDataManager::GetOrOpenCacheStorage( const blink::StorageKey& storage_key, const std::string& unique_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto it = cache_storage_remote_map_.find(unique_id); if (it != cache_storage_remote_map_.end()) { // TODO(enne): should we store `storage_key` so we can DCHECK it matches @@ -110,6 +113,8 @@ const std::string& unique_id, int64_t trace_id, blink::mojom::CacheStorage::OpenCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto& cache_storage = GetOrOpenCacheStorage(storage_key, unique_id); if (!cache_storage) return; @@ -123,6 +128,8 @@ const std::string& unique_id, int64_t trace_id, blink::mojom::CacheStorage::DeleteCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto& cache_storage = GetOrOpenCacheStorage(storage_key, unique_id); if (!cache_storage) return; @@ -137,6 +144,8 @@ const std::string& unique_id, blink::mojom::CacheStorage::DeleteCallback callback, blink::mojom::CacheStorageError result) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Preserve the lifetime of the cache storage remote until here so that this // DidDeleteCache callback will not be dropped. cache_storage_remote_map_.erase(unique_id); @@ -148,6 +157,8 @@ const std::string& unique_id, int64_t trace_id, blink::mojom::CacheStorage::HasCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto& cache_storage = GetOrOpenCacheStorage(storage_key, unique_id); if (!cache_storage) return; @@ -156,12 +167,12 @@ } BackgroundFetchDataManager::~BackgroundFetchDataManager() { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); } void BackgroundFetchDataManager::GetInitializationData( GetInitializationDataCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask(std::make_unique<background_fetch::GetInitializationDataTask>( this, std::move(callback))); @@ -174,7 +185,7 @@ const SkBitmap& icon, bool start_paused, CreateRegistrationCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask(std::make_unique<background_fetch::CreateMetadataTask>( this, registration_id, std::move(requests), std::move(options), icon, @@ -186,7 +197,7 @@ const blink::StorageKey& storage_key, const std::string& developer_id, GetRegistrationCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask(std::make_unique<background_fetch::GetRegistrationTask>( this, service_worker_registration_id, storage_key, developer_id, @@ -196,7 +207,7 @@ void BackgroundFetchDataManager::PopNextRequest( const BackgroundFetchRegistrationId& registration_id, NextRequestCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask( std::make_unique<background_fetch::StartNextPendingRequestTask>( @@ -207,7 +218,7 @@ const BackgroundFetchRegistrationId& registration_id, const scoped_refptr<BackgroundFetchRequestInfo>& request_info, GetRequestBlobCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask(std::make_unique<background_fetch::GetRequestBlobTask>( this, registration_id, request_info, std::move(callback))); @@ -217,7 +228,7 @@ const BackgroundFetchRegistrationId& registration_id, scoped_refptr<BackgroundFetchRequestInfo> request_info, MarkRequestCompleteCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask(std::make_unique<background_fetch::MarkRequestCompleteTask>( this, registration_id, std::move(request_info), std::move(callback))); @@ -227,7 +238,7 @@ const BackgroundFetchRegistrationId& registration_id, std::unique_ptr<BackgroundFetchRequestMatchParams> match_params, SettledFetchesCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask(std::make_unique<background_fetch::MatchRequestsTask>( this, registration_id, std::move(match_params), std::move(callback))); @@ -237,7 +248,7 @@ const BackgroundFetchRegistrationId& registration_id, bool check_for_failure, MarkRegistrationForDeletionCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask( std::make_unique<background_fetch::MarkRegistrationForDeletionTask>( @@ -247,7 +258,7 @@ void BackgroundFetchDataManager::DeleteRegistration( const BackgroundFetchRegistrationId& registration_id, HandleBackgroundFetchErrorCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask(std::make_unique<background_fetch::DeleteRegistrationTask>( this, registration_id.service_worker_registration_id(), @@ -259,20 +270,22 @@ int64_t service_worker_registration_id, const blink::StorageKey& storage_key, blink::mojom::BackgroundFetchService::GetDeveloperIdsCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); AddDatabaseTask(std::make_unique<background_fetch::GetDeveloperIdsTask>( this, service_worker_registration_id, storage_key, std::move(callback))); } -void BackgroundFetchDataManager::ShutdownOnCoreThread() { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); +void BackgroundFetchDataManager::Shutdown() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); shutting_down_ = true; } void BackgroundFetchDataManager::AddDatabaseTask( std::unique_ptr<background_fetch::DatabaseTask> task) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // If Shutdown was called don't add any new tasks. if (shutting_down_) return; @@ -284,7 +297,7 @@ void BackgroundFetchDataManager::OnTaskFinished( background_fetch::DatabaseTask* task) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!database_tasks_.empty()); DCHECK_EQ(database_tasks_.front().get(), task); @@ -295,11 +308,13 @@ } BackgroundFetchDataManager* BackgroundFetchDataManager::data_manager() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return this; } base::WeakPtr<background_fetch::DatabaseTaskHost> BackgroundFetchDataManager::GetWeakPtr() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return weak_ptr_factory_.GetWeakPtr(); }
diff --git a/content/browser/background_fetch/background_fetch_data_manager.h b/content/browser/background_fetch/background_fetch_data_manager.h index 46d3a18..b2d3ea0 100644 --- a/content/browser/background_fetch/background_fetch_data_manager.h +++ b/content/browser/background_fetch/background_fetch_data_manager.h
@@ -19,6 +19,7 @@ #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" +#include "base/sequence_checker.h" #include "content/browser/background_fetch/background_fetch.pb.h" #include "content/browser/background_fetch/background_fetch_registration_id.h" #include "content/browser/background_fetch/background_fetch_scheduler.h" @@ -47,11 +48,10 @@ // for Background Fetch. // // There must only be a single instance of this class per StoragePartition, and -// it must only be used on the service worker core thread, since it relies on -// there being no other code concurrently reading/writing the Background Fetch -// keys of the same Service Worker database (except for deletions, e.g. it's -// safe for the Service Worker code to remove a ServiceWorkerRegistration and -// all its keys). +// it must only be used on the UI thread, since it relies on there being no +// other code concurrently reading/writing the Background Fetch keys of the same +// Service Worker database (except for deletions, e.g. it's safe for the Service +// Worker code to remove a ServiceWorkerRegistration and all its keys). // // Storage schema is documented in storage/README.md class CONTENT_EXPORT BackgroundFetchDataManager @@ -90,7 +90,7 @@ ~BackgroundFetchDataManager() override; // Grabs a reference to CacheStorageManager. - virtual void InitializeOnCoreThread(); + virtual void Initialize(); // Adds or removes the given |observer| to this data manager instance. void AddObserver(BackgroundFetchDataManagerObserver* observer); @@ -182,7 +182,7 @@ return observers_; } - void ShutdownOnCoreThread(); + void Shutdown(); private: FRIEND_TEST_ALL_PREFIXES(BackgroundFetchDataManagerTest, Cleanup); @@ -270,6 +270,8 @@ cache_storage_remote_map_; mojo::Remote<blink::mojom::CacheStorage> null_remote_; + SEQUENCE_CHECKER(sequence_checker_); + base::WeakPtrFactory<BackgroundFetchDataManager> weak_ptr_factory_{this}; DISALLOW_COPY_AND_ASSIGN(BackgroundFetchDataManager);
diff --git a/content/browser/background_fetch/background_fetch_data_manager_unittest.cc b/content/browser/background_fetch/background_fetch_data_manager_unittest.cc index c6027a2a..d092f19 100644 --- a/content/browser/background_fetch/background_fetch_data_manager_unittest.cc +++ b/content/browser/background_fetch/background_fetch_data_manager_unittest.cc
@@ -214,7 +214,7 @@ embedded_worker_test_helper()->context_wrapper()); background_fetch_data_manager_->AddObserver(this); - background_fetch_data_manager_->InitializeOnCoreThread(); + background_fetch_data_manager_->Initialize(); } // Synchronous version of BackgroundFetchDataManager::GetInitializationData().
diff --git a/content/browser/background_fetch/background_fetch_delegate_proxy.cc b/content/browser/background_fetch/background_fetch_delegate_proxy.cc index 31188f67..1f02f6f 100644 --- a/content/browser/background_fetch/background_fetch_delegate_proxy.cc +++ b/content/browser/background_fetch/background_fetch_delegate_proxy.cc
@@ -25,120 +25,105 @@ namespace content { -// Internal functionality of the BackgroundFetchDelegateProxy that lives on the -// UI thread, where all interaction with the download manager must happen. -class BackgroundFetchDelegateProxy::Core - : public BackgroundFetchDelegate::Client { - public: - Core(base::WeakPtr<BackgroundFetchDelegateProxy> parent, - base::WeakPtr<StoragePartitionImpl> storage_partition) - : parent_(std::move(parent)), - storage_partition_(std::move(storage_partition)) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - DCHECK(storage_partition_); +BackgroundFetchDelegateProxy::BackgroundFetchDelegateProxy( + base::WeakPtr<StoragePartitionImpl> storage_partition) + : storage_partition_(std::move(storage_partition)) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +BackgroundFetchDelegateProxy::~BackgroundFetchDelegateProxy() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +void BackgroundFetchDelegateProxy::SetClickEventDispatcher( + DispatchClickEventCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + click_event_dispatcher_callback_ = std::move(callback); +} + +void BackgroundFetchDelegateProxy::GetIconDisplaySize( + BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (auto* delegate = GetDelegate()) { + delegate->GetIconDisplaySize(std::move(callback)); + } else { + std::move(callback).Run(gfx::Size(0, 0)); } +} - ~Core() override { DCHECK_CURRENTLY_ON(BrowserThread::UI); } +void BackgroundFetchDelegateProxy::GetPermissionForOrigin( + const url::Origin& origin, + const WebContents::Getter& wc_getter, + GetPermissionForOriginCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - base::WeakPtr<Core> GetWeakPtrOnUI() { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - return weak_ptr_factory_.GetWeakPtr(); - } + BackgroundFetchPermission result = BackgroundFetchPermission::BLOCKED; - void ForwardGetPermissionForOriginCallbackToParentThread( - GetPermissionForOriginCallback callback, - BackgroundFetchPermission permission) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - RunOrPostTaskOnThread(FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(std::move(callback), permission)); - } - - void GetPermissionForOrigin(const url::Origin& origin, - const WebContents::Getter& wc_getter, - GetPermissionForOriginCallback callback) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - BackgroundFetchPermission result = BackgroundFetchPermission::BLOCKED; - - if (auto* controller = GetPermissionController()) { - content::WebContents* web_contents = - wc_getter ? wc_getter.Run() : nullptr; - content::RenderFrameHost* rfh = - web_contents ? web_contents->GetMainFrame() : nullptr; - blink::mojom::PermissionStatus permission_status = - rfh ? controller->GetPermissionStatusForFrame( - PermissionType::BACKGROUND_FETCH, rfh, - /*requesting_origin=*/origin.GetURL()) - : controller->GetPermissionStatus( - PermissionType::BACKGROUND_FETCH, - /*requesting_origin=*/origin.GetURL(), - /*embedding_origin=*/origin.GetURL()); - switch (permission_status) { - case blink::mojom::PermissionStatus::GRANTED: - result = BackgroundFetchPermission::ALLOWED; - break; - case blink::mojom::PermissionStatus::DENIED: - result = BackgroundFetchPermission::BLOCKED; - break; - case blink::mojom::PermissionStatus::ASK: - result = BackgroundFetchPermission::ASK; - break; - } - } - - RunOrPostTaskOnThread(FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(std::move(callback), result)); - } - - void ForwardGetIconDisplaySizeCallbackToParentThread( - BackgroundFetchDelegate::GetIconDisplaySizeCallback callback, - const gfx::Size& display_size) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - RunOrPostTaskOnThread(FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(std::move(callback), display_size)); - } - - void GetIconDisplaySize( - BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - if (auto* delegate = GetDelegate()) { - delegate->GetIconDisplaySize( - base::BindOnce(&Core::ForwardGetIconDisplaySizeCallbackToParentThread, - weak_ptr_factory_.GetWeakPtr(), std::move(callback))); - } else { - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(std::move(callback), gfx::Size(0, 0))); + if (auto* controller = GetPermissionController()) { + content::WebContents* web_contents = wc_getter ? wc_getter.Run() : nullptr; + content::RenderFrameHost* rfh = + web_contents ? web_contents->GetMainFrame() : nullptr; + blink::mojom::PermissionStatus permission_status = + rfh ? controller->GetPermissionStatusForFrame( + PermissionType::BACKGROUND_FETCH, rfh, + /*requesting_origin=*/origin.GetURL()) + : controller->GetPermissionStatus( + PermissionType::BACKGROUND_FETCH, + /*requesting_origin=*/origin.GetURL(), + /*embedding_origin=*/origin.GetURL()); + switch (permission_status) { + case blink::mojom::PermissionStatus::GRANTED: + result = BackgroundFetchPermission::ALLOWED; + break; + case blink::mojom::PermissionStatus::DENIED: + result = BackgroundFetchPermission::BLOCKED; + break; + case blink::mojom::PermissionStatus::ASK: + result = BackgroundFetchPermission::ASK; + break; } } - void CreateDownloadJob( - std::unique_ptr<BackgroundFetchDescription> fetch_description) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); + std::move(callback).Run(result); +} - auto* delegate = GetDelegate(); - if (delegate) - delegate->CreateDownloadJob(GetWeakPtrOnUI(), - std::move(fetch_description)); +void BackgroundFetchDelegateProxy::CreateDownloadJob( + base::WeakPtr<Controller> controller, + std::unique_ptr<BackgroundFetchDescription> fetch_description) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + DCHECK(!controller_map_.count(fetch_description->job_unique_id)); + controller_map_[fetch_description->job_unique_id] = std::move(controller); + + auto* delegate = GetDelegate(); + if (delegate) { + delegate->CreateDownloadJob(weak_ptr_factory_.GetWeakPtr(), + std::move(fetch_description)); } +} - void StartRequest(const std::string& job_unique_id, - const url::Origin& origin, - const scoped_refptr<BackgroundFetchRequestInfo>& request) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - DCHECK(request); +void BackgroundFetchDelegateProxy::StartRequest( + const std::string& job_unique_id, + const url::Origin& origin, + const scoped_refptr<BackgroundFetchRequestInfo>& request) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - auto* delegate = GetDelegate(); - if (!delegate) - return; + DCHECK(controller_map_.count(job_unique_id)); + DCHECK(request); + DCHECK(!request->download_guid().empty()); - const blink::mojom::FetchAPIRequestPtr& fetch_request = - request->fetch_request(); + auto* delegate = GetDelegate(); + if (!delegate) + return; - const net::NetworkTrafficAnnotationTag traffic_annotation( - net::DefineNetworkTrafficAnnotation("background_fetch_context", - R"( + const blink::mojom::FetchAPIRequestPtr& fetch_request = + request->fetch_request(); + + const net::NetworkTrafficAnnotationTag traffic_annotation( + net::DefineNetworkTrafficAnnotation("background_fetch_context", + R"( semantics { sender: "Background Fetch API" description: @@ -165,264 +150,26 @@ policy_exception_justification: "Not implemented." })")); - // TODO(peter): The |headers| should be populated with all the properties - // set in the |fetch_request| structure. - net::HttpRequestHeaders headers; - for (const auto& pair : fetch_request->headers) - headers.SetHeader(pair.first, pair.second); + // TODO(peter): The |headers| should be populated with all the properties + // set in the |fetch_request| structure. + net::HttpRequestHeaders headers; + for (const auto& pair : fetch_request->headers) + headers.SetHeader(pair.first, pair.second); - // Append the Origin header for requests whose CORS flag is set, or whose - // request method is not GET or HEAD. See section 3.1 of the standard: - // https://fetch.spec.whatwg.org/#origin-header - if (fetch_request->mode == network::mojom::RequestMode::kCors || - fetch_request->mode == - network::mojom::RequestMode::kCorsWithForcedPreflight || - (fetch_request->method != "GET" && fetch_request->method != "HEAD")) { - headers.SetHeader("Origin", origin.Serialize()); - } - - delegate->DownloadUrl( - job_unique_id, request->download_guid(), fetch_request->method, - fetch_request->url, traffic_annotation, headers, - /* has_request_body= */ request->request_body_size() > 0u); + // Append the Origin header for requests whose CORS flag is set, or whose + // request method is not GET or HEAD. See section 3.1 of the standard: + // https://fetch.spec.whatwg.org/#origin-header + if (fetch_request->mode == network::mojom::RequestMode::kCors || + fetch_request->mode == + network::mojom::RequestMode::kCorsWithForcedPreflight || + (fetch_request->method != "GET" && fetch_request->method != "HEAD")) { + headers.SetHeader("Origin", origin.Serialize()); } - void Abort(const std::string& job_unique_id) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - if (auto* delegate = GetDelegate()) - delegate->Abort(job_unique_id); - } - - void MarkJobComplete(const std::string& job_unique_id) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - if (auto* delegate = GetDelegate()) - delegate->MarkJobComplete(job_unique_id); - } - - void UpdateUI(const std::string& job_unique_id, - const absl::optional<std::string>& title, - const absl::optional<SkBitmap>& icon) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - if (auto* delegate = GetDelegate()) - delegate->UpdateUI(job_unique_id, title, icon); - } - - // BackgroundFetchDelegate::Client implementation: - void OnJobCancelled( - const std::string& job_unique_id, - blink::mojom::BackgroundFetchFailureReason reason_to_abort) override; - void OnDownloadUpdated(const std::string& job_unique_id, - const std::string& guid, - uint64_t bytes_uploaded, - uint64_t bytes_downloaded) override; - void OnDownloadComplete( - const std::string& job_unique_id, - const std::string& guid, - std::unique_ptr<BackgroundFetchResult> result) override; - void OnDownloadStarted( - const std::string& job_unique_id, - const std::string& guid, - std::unique_ptr<content::BackgroundFetchResponse> response) override; - void OnUIActivated(const std::string& unique_id) override; - void OnUIUpdated(const std::string& unique_id) override; - void GetUploadData( - const std::string& job_unique_id, - const std::string& download_guid, - BackgroundFetchDelegate::GetUploadDataCallback callback) override; - - private: - BrowserContext* GetBrowserContext() { - if (!storage_partition_) - return nullptr; - return storage_partition_->browser_context(); - } - - BackgroundFetchDelegate* GetDelegate() { - auto* browser_context = GetBrowserContext(); - if (!browser_context) - return nullptr; - return browser_context->GetBackgroundFetchDelegate(); - } - - PermissionControllerImpl* GetPermissionController() { - auto* browser_context = GetBrowserContext(); - if (!browser_context) - return nullptr; - return PermissionControllerImpl::FromBrowserContext(browser_context); - } - - // Weak reference to the service worker core thread outer class that owns us. - base::WeakPtr<BackgroundFetchDelegateProxy> parent_; - - base::WeakPtr<StoragePartitionImpl> storage_partition_; - - base::WeakPtrFactory<Core> weak_ptr_factory_{this}; - - DISALLOW_COPY_AND_ASSIGN(Core); -}; - -void BackgroundFetchDelegateProxy::Core::OnJobCancelled( - const std::string& job_unique_id, - blink::mojom::BackgroundFetchFailureReason reason_to_abort) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(&BackgroundFetchDelegateProxy::OnJobCancelled, parent_, - job_unique_id, reason_to_abort)); -} - -void BackgroundFetchDelegateProxy::Core::OnDownloadUpdated( - const std::string& job_unique_id, - const std::string& guid, - uint64_t bytes_uploaded, - uint64_t bytes_downloaded) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(&BackgroundFetchDelegateProxy::OnDownloadUpdated, parent_, - job_unique_id, guid, bytes_uploaded, bytes_downloaded)); -} - -void BackgroundFetchDelegateProxy::Core::OnDownloadComplete( - const std::string& job_unique_id, - const std::string& guid, - std::unique_ptr<BackgroundFetchResult> result) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(&BackgroundFetchDelegateProxy::OnDownloadComplete, parent_, - job_unique_id, guid, std::move(result))); -} - -void BackgroundFetchDelegateProxy::Core::OnDownloadStarted( - const std::string& job_unique_id, - const std::string& guid, - std::unique_ptr<content::BackgroundFetchResponse> response) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(&BackgroundFetchDelegateProxy::DidStartRequest, parent_, - job_unique_id, guid, std::move(response))); -} - -void BackgroundFetchDelegateProxy::Core::OnUIActivated( - const std::string& job_unique_id) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(&BackgroundFetchDelegateProxy::DidActivateUI, parent_, - job_unique_id)); -} - -void BackgroundFetchDelegateProxy::Core::OnUIUpdated( - const std::string& job_unique_id) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(&BackgroundFetchDelegateProxy::DidUpdateUI, parent_, - job_unique_id)); -} - -void BackgroundFetchDelegateProxy::Core::GetUploadData( - const std::string& job_unique_id, - const std::string& download_guid, - BackgroundFetchDelegate::GetUploadDataCallback callback) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - // Pass this to the core thread for processing, but wrap |callback| - // to be posted back to the UI thread when executed. - BackgroundFetchDelegate::GetUploadDataCallback wrapped_callback = - base::BindOnce( - [](BackgroundFetchDelegate::GetUploadDataCallback callback, - blink::mojom::SerializedBlobPtr blob) { - GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), std::move(blob))); - }, - std::move(callback)); - - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce(&BackgroundFetchDelegateProxy::GetUploadData, parent_, - job_unique_id, download_guid, - std::move(wrapped_callback))); -} - -BackgroundFetchDelegateProxy::BackgroundFetchDelegateProxy( - base::WeakPtr<StoragePartitionImpl> storage_partition) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - // Normally it would be unsafe to obtain a weak pointer on the UI thread from - // a factory that lives on the service worker core thread, but it's ok in the - // constructor as |this| can't be destroyed before the constructor finishes. - ui_core_.reset( - new Core(weak_ptr_factory_.GetWeakPtr(), std::move(storage_partition))); - - // Since this constructor runs on the UI thread, a WeakPtr can be safely - // obtained from the Core. - ui_core_ptr_ = ui_core_->GetWeakPtrOnUI(); -} - -BackgroundFetchDelegateProxy::~BackgroundFetchDelegateProxy() { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); -} - -void BackgroundFetchDelegateProxy::SetClickEventDispatcher( - DispatchClickEventCallback callback) { - click_event_dispatcher_callback_ = std::move(callback); -} - -void BackgroundFetchDelegateProxy::GetIconDisplaySize( - BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); - RunOrPostTaskOnThread(FROM_HERE, BrowserThread::UI, - base::BindOnce(&Core::GetIconDisplaySize, ui_core_ptr_, - std::move(callback))); -} - -void BackgroundFetchDelegateProxy::GetPermissionForOrigin( - const url::Origin& origin, - const WebContents::Getter& wc_getter, - GetPermissionForOriginCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); - RunOrPostTaskOnThread( - FROM_HERE, BrowserThread::UI, - base::BindOnce(&Core::GetPermissionForOrigin, ui_core_ptr_, origin, - wc_getter, std::move(callback))); -} - -void BackgroundFetchDelegateProxy::CreateDownloadJob( - base::WeakPtr<Controller> controller, - std::unique_ptr<BackgroundFetchDescription> fetch_description) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); - - DCHECK(!controller_map_.count(fetch_description->job_unique_id)); - controller_map_[fetch_description->job_unique_id] = std::move(controller); - - RunOrPostTaskOnThread(FROM_HERE, BrowserThread::UI, - base::BindOnce(&Core::CreateDownloadJob, ui_core_ptr_, - std::move(fetch_description))); -} - -void BackgroundFetchDelegateProxy::StartRequest( - const std::string& job_unique_id, - const url::Origin& origin, - const scoped_refptr<BackgroundFetchRequestInfo>& request) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); - - DCHECK(controller_map_.count(job_unique_id)); - DCHECK(request); - DCHECK(!request->download_guid().empty()); - - RunOrPostTaskOnThread(FROM_HERE, BrowserThread::UI, - base::BindOnce(&Core::StartRequest, ui_core_ptr_, - job_unique_id, origin, request)); + delegate->DownloadUrl( + job_unique_id, request->download_guid(), fetch_request->method, + fetch_request->url, traffic_annotation, headers, + /* has_request_body= */ request->request_body_size() > 0u); } void BackgroundFetchDelegateProxy::UpdateUI( @@ -431,36 +178,35 @@ const absl::optional<SkBitmap>& icon, blink::mojom::BackgroundFetchRegistrationService::UpdateUICallback update_ui_callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!update_ui_callback_map_.count(job_unique_id)); update_ui_callback_map_.emplace(job_unique_id, std::move(update_ui_callback)); - RunOrPostTaskOnThread(FROM_HERE, BrowserThread::UI, - base::BindOnce(&Core::UpdateUI, ui_core_ptr_, - job_unique_id, title, icon)); + if (auto* delegate = GetDelegate()) + delegate->UpdateUI(job_unique_id, title, icon); } void BackgroundFetchDelegateProxy::Abort(const std::string& job_unique_id) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); - - RunOrPostTaskOnThread( - FROM_HERE, BrowserThread::UI, - base::BindOnce(&Core::Abort, ui_core_ptr_, job_unique_id)); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (auto* delegate = GetDelegate()) + delegate->Abort(job_unique_id); } void BackgroundFetchDelegateProxy::MarkJobComplete( const std::string& job_unique_id) { - RunOrPostTaskOnThread( - FROM_HERE, BrowserThread::UI, - base::BindOnce(&Core::MarkJobComplete, ui_core_ptr_, job_unique_id)); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (auto* delegate = GetDelegate()) + delegate->MarkJobComplete(job_unique_id); + controller_map_.erase(job_unique_id); } void BackgroundFetchDelegateProxy::OnJobCancelled( const std::string& job_unique_id, blink::mojom::BackgroundFetchFailureReason reason_to_abort) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK( reason_to_abort == blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI || @@ -475,11 +221,11 @@ controller->AbortFromDelegate(reason_to_abort); } -void BackgroundFetchDelegateProxy::DidStartRequest( +void BackgroundFetchDelegateProxy::OnDownloadStarted( const std::string& job_unique_id, const std::string& guid, std::unique_ptr<BackgroundFetchResponse> response) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); auto it = controller_map_.find(job_unique_id); if (it == controller_map_.end()) @@ -489,14 +235,16 @@ controller->DidStartRequest(guid, std::move(response)); } -void BackgroundFetchDelegateProxy::DidActivateUI( +void BackgroundFetchDelegateProxy::OnUIActivated( const std::string& job_unique_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(click_event_dispatcher_callback_); click_event_dispatcher_callback_.Run(job_unique_id); } -void BackgroundFetchDelegateProxy::DidUpdateUI( +void BackgroundFetchDelegateProxy::OnUIUpdated( const std::string& job_unique_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); auto it = update_ui_callback_map_.find(job_unique_id); if (it == update_ui_callback_map_.end()) return; @@ -511,7 +259,7 @@ const std::string& guid, uint64_t bytes_uploaded, uint64_t bytes_downloaded) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); auto it = controller_map_.find(job_unique_id); if (it == controller_map_.end()) @@ -525,7 +273,7 @@ const std::string& job_unique_id, const std::string& guid, std::unique_ptr<BackgroundFetchResult> result) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); auto it = controller_map_.find(job_unique_id); if (it == controller_map_.end()) @@ -539,6 +287,8 @@ const std::string& job_unique_id, const std::string& download_guid, BackgroundFetchDelegate::GetUploadDataCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto it = controller_map_.find(job_unique_id); if (it == controller_map_.end()) { std::move(callback).Run(nullptr); @@ -551,4 +301,25 @@ std::move(callback).Run(nullptr); } +BrowserContext* BackgroundFetchDelegateProxy::GetBrowserContext() { + if (!storage_partition_) + return nullptr; + return storage_partition_->browser_context(); +} + +BackgroundFetchDelegate* BackgroundFetchDelegateProxy::GetDelegate() { + auto* browser_context = GetBrowserContext(); + if (!browser_context) + return nullptr; + return browser_context->GetBackgroundFetchDelegate(); +} + +PermissionControllerImpl* +BackgroundFetchDelegateProxy::GetPermissionController() { + auto* browser_context = GetBrowserContext(); + if (!browser_context) + return nullptr; + return PermissionControllerImpl::FromBrowserContext(browser_context); +} + } // namespace content
diff --git a/content/browser/background_fetch/background_fetch_delegate_proxy.h b/content/browser/background_fetch/background_fetch_delegate_proxy.h index 4b2ca68..d76bf7d 100644 --- a/content/browser/background_fetch/background_fetch_delegate_proxy.h +++ b/content/browser/background_fetch/background_fetch_delegate_proxy.h
@@ -14,6 +14,7 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "base/sequence_checker.h" #include "content/browser/background_fetch/background_fetch_request_info.h" #include "content/public/browser/background_fetch_delegate.h" #include "content/public/browser/background_fetch_description.h" @@ -26,17 +27,18 @@ namespace content { +class PermissionControllerImpl; class StoragePartitionImpl; -// Proxy class for passing messages between BackgroundFetchJobControllers on the -// service worker core thread and BackgroundFetchDelegate on the UI thread. +// This class was previously responsible for passing messages between +// BackgroundFetchJobControllers on the service worker core thread and +// BackgroundFetchDelegate on the UI thread. It may no longer be needed +// now that these are the same thread. // -// TODO(crbug.com/824858): This proxying should no longer be needed after -// the service worker core thread becomes the UI thread. -class CONTENT_EXPORT BackgroundFetchDelegateProxy { +// Lives on the UI thread. +class CONTENT_EXPORT BackgroundFetchDelegateProxy + : public BackgroundFetchDelegate::Client { public: - // Subclasses must only be destroyed on the service worker core thread, since - // these methods will be called on the service worker core thread. using DispatchClickEventCallback = base::RepeatingCallback<void(const std::string& unique_id)>; using GetPermissionForOriginCallback = @@ -77,7 +79,7 @@ explicit BackgroundFetchDelegateProxy( base::WeakPtr<StoragePartitionImpl> storage_partition); - ~BackgroundFetchDelegateProxy(); + ~BackgroundFetchDelegateProxy() override; // Set BackgroundFetchClick event dispatcher callback, which is a method on // the background fetch context. @@ -99,22 +101,20 @@ // browser session, then |fetch_description->current_guids| should contain the // GUIDs of in progress downloads, while completed downloads are recorded in // |fetch_description->completed_requests|. - // Should only be called from the Controller (on the service worker core - // thread). + // Should only be called from the Controller. void CreateDownloadJob( base::WeakPtr<Controller> controller, std::unique_ptr<BackgroundFetchDescription> fetch_description); // Requests that the download manager start fetching |request|. - // Should only be called from the Controller (on the service worker core - // thread). + // Should only be called from the Controller. void StartRequest(const std::string& job_unique_id, const url::Origin& origin, const scoped_refptr<BackgroundFetchRequestInfo>& request); // Updates the representation of this registration in the user interface to // match the given |title| or |icon|. - // Called from the Controller (on the service worker core thread). + // Called from the Controller. void UpdateUI( const std::string& job_unique_id, const absl::optional<std::string>& title, @@ -123,8 +123,8 @@ update_ui_callback); // Aborts in progress downloads for the given registration. Called from the - // Controller (on the service worker core thread) after it is aborted. May - // occur even if all requests already called OnDownloadComplete. + // Controller after it is aborted. May occur even if all requests already + // called OnDownloadComplete. void Abort(const std::string& job_unique_id); // Called when the fetch associated |job_unique_id| is completed. @@ -134,50 +134,34 @@ void Shutdown(); private: - class Core; - - // Called when the job identified by |job_unique|id| was cancelled by the - // delegate. Should only be called on the service worker core thread. + // BackgroundFetchDelegate::Client implementation: void OnJobCancelled( const std::string& job_unique_id, - blink::mojom::BackgroundFetchFailureReason reason_to_abort); - - // Called when the download identified by |guid| has succeeded/failed/aborted. - // Should only be called on the service worker core thread. - void OnDownloadComplete(const std::string& job_unique_id, - const std::string& guid, - std::unique_ptr<BackgroundFetchResult> result); - - // Called when progress has been made for the download identified by |guid|. - // Progress is either the request body being uploaded or response body being - // downloaded. Should only be called on the service worker core thread. + blink::mojom::BackgroundFetchFailureReason reason_to_abort) override; + void OnDownloadComplete( + const std::string& job_unique_id, + const std::string& guid, + std::unique_ptr<BackgroundFetchResult> result) override; void OnDownloadUpdated(const std::string& job_unique_id, const std::string& guid, uint64_t bytes_uploaded, - uint64_t bytes_downloaded); + uint64_t bytes_downloaded) override; + void OnDownloadStarted( + const std::string& job_unique_id, + const std::string& guid, + std::unique_ptr<BackgroundFetchResponse> response) override; + void OnUIActivated(const std::string& job_unique_id) override; + void OnUIUpdated(const std::string& job_unique_id) override; + void GetUploadData( + const std::string& job_unique_id, + const std::string& download_guid, + BackgroundFetchDelegate::GetUploadDataCallback callback) override; - // Should only be called from the BackgroundFetchDelegate (on the service - // worker core thread). - void DidStartRequest(const std::string& job_unique_id, - const std::string& guid, - std::unique_ptr<BackgroundFetchResponse> response); + BrowserContext* GetBrowserContext(); - // Should only be called from the BackgroundFetchDelegate (on the service - // worker core thread). - void DidActivateUI(const std::string& job_unique_id); + BackgroundFetchDelegate* GetDelegate(); - // Should only be called from the BackgroundFetchDelegate (on the service - // worker core thread). - void DidUpdateUI(const std::string& job_unique_id); - - // Should only be called from the BackgroundFetchDelegate (on the service - // worker core thread). - void GetUploadData(const std::string& job_unique_id, - const std::string& download_guid, - BackgroundFetchDelegate::GetUploadDataCallback callback); - - std::unique_ptr<Core, BrowserThread::DeleteOnUIThread> ui_core_; - base::WeakPtr<Core> ui_core_ptr_; + PermissionControllerImpl* GetPermissionController(); // Map from unique job ids to the controller. std::map<std::string, base::WeakPtr<Controller>> controller_map_; @@ -188,6 +172,11 @@ update_ui_callback_map_; DispatchClickEventCallback click_event_dispatcher_callback_; + + base::WeakPtr<StoragePartitionImpl> storage_partition_; + + SEQUENCE_CHECKER(sequence_checker_); + base::WeakPtrFactory<BackgroundFetchDelegateProxy> weak_ptr_factory_{this}; DISALLOW_COPY_AND_ASSIGN(BackgroundFetchDelegateProxy);
diff --git a/content/browser/background_fetch/background_fetch_scheduler_unittest.cc b/content/browser/background_fetch/background_fetch_scheduler_unittest.cc index 32b88d2d..1650520 100644 --- a/content/browser/background_fetch/background_fetch_scheduler_unittest.cc +++ b/content/browser/background_fetch/background_fetch_scheduler_unittest.cc
@@ -78,7 +78,7 @@ data_manager_ = std::make_unique<BackgroundFetchTestDataManager>( browser_context(), storage_partition(), embedded_worker_test_helper()->context_wrapper()); - data_manager_->InitializeOnCoreThread(); + data_manager_->Initialize(); delegate_proxy_ = std::make_unique<BackgroundFetchDelegateProxy>(storage_partition());
diff --git a/content/browser/background_fetch/background_fetch_service_impl.cc b/content/browser/background_fetch/background_fetch_service_impl.cc index 3951032..f61a180 100644 --- a/content/browser/background_fetch/background_fetch_service_impl.cc +++ b/content/browser/background_fetch/background_fetch_service_impl.cc
@@ -46,16 +46,15 @@ if (!render_process_host) return; - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce( - BackgroundFetchServiceImpl::CreateOnCoreThread, - WrapRefCounted(static_cast<StoragePartitionImpl*>( - render_process_host->GetStoragePartition()) - ->GetBackgroundFetchContext()), - info.storage_key, - /* render_frame_tree_node_id= */ 0, - /* wc_getter= */ base::NullCallback(), std::move(receiver))); + scoped_refptr<BackgroundFetchContext> context = + WrapRefCounted(static_cast<StoragePartitionImpl*>( + render_process_host->GetStoragePartition()) + ->GetBackgroundFetchContext()); + mojo::MakeSelfOwnedReceiver(std::make_unique<BackgroundFetchServiceImpl>( + std::move(context), info.storage_key, + /*render_frame_tree_node_id=*/0, + /*wc_getter=*/base::NullCallback()), + std::move(receiver)); } // static @@ -64,43 +63,27 @@ mojo::PendingReceiver<blink::mojom::BackgroundFetchService> receiver) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(render_frame_host); - RenderProcessHost* render_process_host = render_frame_host->GetProcess(); + auto* rfhi = static_cast<RenderFrameHostImpl*>(render_frame_host); + RenderProcessHost* render_process_host = rfhi->GetProcess(); DCHECK(render_process_host); WebContents::Getter wc_getter = base::NullCallback(); // Permissions need to go through the DownloadRequestLimiter if the fetch // is started from a top-level frame. - if (!render_frame_host->GetParent()) { + if (!rfhi->GetParent()) { wc_getter = base::BindRepeating(&WebContents::FromFrameTreeNodeId, - render_frame_host->GetFrameTreeNodeId()); + rfhi->GetFrameTreeNodeId()); } - RunOrPostTaskOnThread( - FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), - base::BindOnce( - BackgroundFetchServiceImpl::CreateOnCoreThread, - WrapRefCounted(static_cast<StoragePartitionImpl*>( - render_process_host->GetStoragePartition()) - ->GetBackgroundFetchContext()), - static_cast<RenderFrameHostImpl*>(render_frame_host)->storage_key(), - render_frame_host->GetFrameTreeNodeId(), std::move(wc_getter), - std::move(receiver))); -} - -// static -void BackgroundFetchServiceImpl::CreateOnCoreThread( - scoped_refptr<BackgroundFetchContext> background_fetch_context, - blink::StorageKey storage_key, - int render_frame_tree_node_id, - WebContents::Getter wc_getter, - mojo::PendingReceiver<blink::mojom::BackgroundFetchService> receiver) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); - + scoped_refptr<BackgroundFetchContext> context = + WrapRefCounted(static_cast<StoragePartitionImpl*>( + render_process_host->GetStoragePartition()) + ->GetBackgroundFetchContext()); mojo::MakeSelfOwnedReceiver( std::make_unique<BackgroundFetchServiceImpl>( - std::move(background_fetch_context), std::move(storage_key), - render_frame_tree_node_id, std::move(wc_getter)), + std::move(context), rfhi->storage_key(), rfhi->GetFrameTreeNodeId(), + std::move(wc_getter)), std::move(receiver)); } @@ -113,11 +96,13 @@ storage_key_(std::move(storage_key)), render_frame_tree_node_id_(render_frame_tree_node_id), wc_getter_(std::move(wc_getter)) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(background_fetch_context_); } BackgroundFetchServiceImpl::~BackgroundFetchServiceImpl() { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); } void BackgroundFetchServiceImpl::Fetch( @@ -128,7 +113,7 @@ const SkBitmap& icon, blink::mojom::BackgroundFetchUkmDataPtr ukm_data, FetchCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!ValidateDeveloperId(developer_id) || !ValidateRequests(requests)) { std::move(callback).Run( @@ -151,7 +136,7 @@ void BackgroundFetchServiceImpl::GetIconDisplaySize( blink::mojom::BackgroundFetchService::GetIconDisplaySizeCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); background_fetch_context_->GetIconDisplaySize(std::move(callback)); } @@ -159,7 +144,7 @@ int64_t service_worker_registration_id, const std::string& developer_id, GetRegistrationCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!ValidateDeveloperId(developer_id)) { std::move(callback).Run( blink::mojom::BackgroundFetchError::INVALID_ARGUMENT, @@ -175,13 +160,15 @@ void BackgroundFetchServiceImpl::GetDeveloperIds( int64_t service_worker_registration_id, GetDeveloperIdsCallback callback) { - DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); background_fetch_context_->GetDeveloperIdsForServiceWorker( service_worker_registration_id, storage_key_, std::move(callback)); } bool BackgroundFetchServiceImpl::ValidateDeveloperId( const std::string& developer_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (developer_id.empty() || developer_id.size() > kMaxDeveloperIdLength) { mojo::ReportBadMessage("Invalid developer_id"); return false; @@ -192,6 +179,8 @@ bool BackgroundFetchServiceImpl::ValidateUniqueId( const std::string& unique_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!base::IsValidGUIDOutputString(unique_id)) { mojo::ReportBadMessage("Invalid unique_id"); return false; @@ -202,6 +191,8 @@ bool BackgroundFetchServiceImpl::ValidateRequests( const std::vector<blink::mojom::FetchAPIRequestPtr>& requests) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (requests.empty()) { mojo::ReportBadMessage("Invalid requests"); return false;
diff --git a/content/browser/background_fetch/background_fetch_service_impl.h b/content/browser/background_fetch/background_fetch_service_impl.h index a3c48f5..2fcc822 100644 --- a/content/browser/background_fetch/background_fetch_service_impl.h +++ b/content/browser/background_fetch/background_fetch_service_impl.h
@@ -12,6 +12,7 @@ #include "base/compiler_specific.h" #include "base/macros.h" #include "base/memory/ref_counted.h" +#include "base/sequence_checker.h" #include "content/browser/background_fetch/background_fetch_context.h" #include "content/common/content_export.h" #include "third_party/blink/public/common/storage_key/storage_key.h" @@ -55,13 +56,6 @@ GetDeveloperIdsCallback callback) override; private: - static void CreateOnCoreThread( - scoped_refptr<BackgroundFetchContext> background_fetch_context, - blink::StorageKey storage_key, - int render_frame_tree_node_id, - WebContents::Getter wc_getter, - mojo::PendingReceiver<blink::mojom::BackgroundFetchService> receiver); - // Validates and returns whether the |developer_id|, |unique_id|, |requests| // and |title| respectively have valid values. The renderer will be flagged // for having sent a bad message if the values are invalid. @@ -77,6 +71,7 @@ int render_frame_tree_node_id_; WebContents::Getter wc_getter_; + SEQUENCE_CHECKER(sequence_checker_); DISALLOW_COPY_AND_ASSIGN(BackgroundFetchServiceImpl); };
diff --git a/content/browser/background_fetch/background_fetch_service_unittest.cc b/content/browser/background_fetch/background_fetch_service_unittest.cc index cbf7eea..7c5f78c 100644 --- a/content/browser/background_fetch/background_fetch_service_unittest.cc +++ b/content/browser/background_fetch/background_fetch_service_unittest.cc
@@ -304,7 +304,7 @@ browser_context()->SetPermissionControllerDelegate( std::move(mock_permission_manager)); - context_->InitializeOnCoreThread(); + context_->Initialize(); service_ = std::make_unique<BackgroundFetchServiceImpl>( context_, storage_key(), /* render_frame_tree_node_id= */ 0,
diff --git a/content/browser/background_fetch/background_fetch_test_data_manager.cc b/content/browser/background_fetch/background_fetch_test_data_manager.cc index 837f75ad4..65b9c87 100644 --- a/content/browser/background_fetch/background_fetch_test_data_manager.cc +++ b/content/browser/background_fetch/background_fetch_test_data_manager.cc
@@ -70,7 +70,7 @@ browser_context_(browser_context), storage_partition_(storage_partition) {} -void BackgroundFetchTestDataManager::InitializeOnCoreThread() { +void BackgroundFetchTestDataManager::Initialize() { // CacheStorage uses the default QuotaManager and not the mock one in this // class. Set QuotaSettings appropriately so that all platforms have quota. // The mock one is still used for testing quota exceeded scenarios in
diff --git a/content/browser/background_fetch/background_fetch_test_data_manager.h b/content/browser/background_fetch/background_fetch_test_data_manager.h index a1e91d1..9c20479 100644 --- a/content/browser/background_fetch/background_fetch_test_data_manager.h +++ b/content/browser/background_fetch/background_fetch_test_data_manager.h
@@ -37,7 +37,7 @@ ~BackgroundFetchTestDataManager() override; - void InitializeOnCoreThread() override; + void Initialize() override; private: friend class BackgroundFetchDataManagerTest;
diff --git a/content/browser/browsing_instance.cc b/content/browser/browsing_instance.cc index a56ff6b..63ec7e7 100644 --- a/content/browser/browsing_instance.cc +++ b/content/browser/browsing_instance.cc
@@ -108,6 +108,19 @@ DCHECK(site_instance->browsing_instance_.get() == this); DCHECK(site_instance->HasSite()); + // Verify that the SiteInstance's StoragePartitionConfig matches this + // BrowsingInstance's StoragePartitionConfig if it already has one. + const StoragePartitionConfig& storage_partition_config = + site_instance->GetSiteInfo().storage_partition_config(); + if (storage_partition_config_.has_value()) { + // We should only use a single StoragePartition within a BrowsingInstance. + // If we're attempting to use multiple, something has gone wrong with the + // logic at upper layers. + CHECK_EQ(storage_partition_config_.value(), storage_partition_config); + } else { + storage_partition_config_ = storage_partition_config; + } + // Explicitly prevent the default SiteInstance from being added since // the map is only supposed to contain instances that map to a single site. if (site_instance->IsDefaultSiteInstance()) { @@ -170,7 +183,21 @@ SiteInfo BrowsingInstance::ComputeSiteInfoForURL( const UrlInfo& url_info) const { - return SiteInfo::Create(isolation_context_, url_info, + // If a StoragePartitionConfig is specified in both |url_info| and this + // BrowsingInstance, make sure they match. + if (url_info.storage_partition_config.has_value() && + storage_partition_config_.has_value()) { + CHECK_EQ(storage_partition_config_.value(), + url_info.storage_partition_config.value()); + } + // If no StoragePartitionConfig was set in |url_info|, create a new UrlInfo + // that inherit's this BrowsingInstance's StoragePartitionConfig. + UrlInfo url_info_with_partition = + url_info.storage_partition_config.has_value() + ? url_info + : url_info.CreateCopyWithStoragePartitionConfig( + storage_partition_config_); + return SiteInfo::Create(isolation_context_, url_info_with_partition, web_exposed_isolation_info_); }
diff --git a/content/browser/browsing_instance.h b/content/browser/browsing_instance.h index 119cce27..2d66f5a 100644 --- a/content/browser/browsing_instance.h +++ b/content/browser/browsing_instance.h
@@ -18,6 +18,8 @@ #include "content/common/content_export.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/render_process_host_observer.h" +#include "content/public/browser/storage_partition_config.h" +#include "third_party/abseil-cpp/absl/types/optional.h" #include "url/origin.h" class GURL; @@ -230,6 +232,15 @@ // and if so, from which top level origin. const WebExposedIsolationInfo web_exposed_isolation_info_; + // The StoragePartitionConfig that must be used by all SiteInstances in this + // BrowsingInstance. This will be set to the StoragePartitionConfig of the + // first SiteInstance that has its SiteInfo assigned in this + // BrowsingInstance, and cannot be changed afterwards. + // + // See crbug.com/1212266 for more context on why we track the + // StoragePartitionConfig here. + absl::optional<StoragePartitionConfig> storage_partition_config_; + DISALLOW_COPY_AND_ASSIGN(BrowsingInstance); };
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc index 9bbca7c..4dd0448 100644 --- a/content/browser/isolated_origin_browsertest.cc +++ b/content/browser/isolated_origin_browsertest.cc
@@ -2918,9 +2918,13 @@ // A SiteInstance created for an isolated origin ServiceWorker should // not reuse the unsuitable first process. + BrowserContext* browser_context = web_contents()->GetBrowserContext(); scoped_refptr<SiteInstanceImpl> sw_site_instance = SiteInstanceImpl::CreateForServiceWorker( - web_contents()->GetBrowserContext(), hung_isolated_url, + browser_context, + UrlInfo::CreateForTesting( + hung_isolated_url, + StoragePartitionConfig::CreateDefault(browser_context)), WebExposedIsolationInfo::CreateNonIsolated(), /* can_reuse_process= */ true); RenderProcessHost* sw_host = sw_site_instance->GetProcess();
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc index 70b9878..4937a7e 100644 --- a/content/browser/renderer_host/render_frame_host_manager.cc +++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -47,6 +47,7 @@ #include "content/browser/renderer_host/render_widget_host_view_base.h" #include "content/browser/renderer_host/render_widget_host_view_child_frame.h" #include "content/browser/site_instance_impl.h" +#include "content/browser/storage_partition_impl.h" #include "content/browser/webui/web_ui_controller_factory_registry.h" #include "content/common/content_navigation_policy.h" #include "content/common/navigation_params_utils.h" @@ -135,6 +136,20 @@ return future_site_info.RequiresDedicatedProcess(future_isolation_context); } +// Helper function to determine whether |dest_url_info| should be loaded in the +// same StoragePartition that |current_instance| is currently using. +bool DoesNavigationChangeStoragePartition(SiteInstanceImpl* current_instance, + const UrlInfo& dest_url_info) { + // Derive a new SiteInfo from |current_instance|, but don't treat the + // navigation as related to avoid StoragePartition propagation logic. + StoragePartitionConfig dest_partition_config = + current_instance->DeriveSiteInfo(dest_url_info, /*is_related=*/false) + .storage_partition_config(); + StoragePartitionConfig current_partition_config = + current_instance->GetSiteInfo().storage_partition_config(); + return current_partition_config != dest_partition_config; +} + bool IsSiteInstanceCompatibleWithErrorIsolation(SiteInstance* site_instance, bool is_main_frame, bool is_failure) { @@ -1515,7 +1530,8 @@ bool is_speculative) { const GURL& destination_url = destination_url_info.url; // A subframe must stay in the same BrowsingInstance as its parent. - if (!frame_tree_node_->IsMainFrame()) + bool is_main_frame = frame_tree_node_->IsMainFrame(); + if (!is_main_frame) return ShouldSwapBrowsingInstance::kNo_NotMainFrame; if (is_same_document) @@ -1530,8 +1546,7 @@ // when we are changing RenderFrames. Remove this when autoreload logic is // updated to handle different RenderFrames correctly. if (is_failure && is_reload && - SiteIsolationPolicy::IsErrorPageIsolationEnabled( - frame_tree_node_->IsMainFrame())) { + SiteIsolationPolicy::IsErrorPageIsolationEnabled(is_main_frame)) { return ShouldSwapBrowsingInstance::kNo_ReloadingErrorPage; } @@ -1650,8 +1665,9 @@ // relationship for it (for isolated error pages). // See https://crbug.com/803367. bool is_for_isolated_error_page = - is_failure && SiteIsolationPolicy::IsErrorPageIsolationEnabled( - frame_tree_node_->IsMainFrame()); + is_failure && + SiteIsolationPolicy::IsErrorPageIsolationEnabled(is_main_frame); + if (current_instance->HasSite() && !render_frame_host_->IsNavigationSameSite(destination_url_info, web_exposed_isolation_info) && @@ -1665,6 +1681,22 @@ return ShouldSwapBrowsingInstance::kYes_ForceSwap; } + // If the navigation should end up in a different StoragePartition, create a + // new BrowsingInstance, as we can only have one StoragePartition per + // BrowsingInstance. + // + // Skip this check if effective URLs are involved. This ensures same-site + // navigations to non-app sites from an isolated hosted app stay in the same + // StoragePartition and process. This behavior is covered in + // IsolatedAppTest.IsolatedAppProcessModel. + if (DoesNavigationChangeStoragePartition(current_instance, + destination_url_info) && + !current_instance + ->IsNavigationAllowedToStayInSameProcessDueToEffectiveURLs( + browser_context, is_main_frame, destination_url_info.url)) { + return ShouldSwapBrowsingInstance::kYes_ForceSwap; + } + // Experimental mode to swap BrowsingInstances on most navigations when there // are no other windows in the BrowsingInstance. return ShouldProactivelySwapBrowsingInstance( @@ -2086,7 +2118,7 @@ // If the entry has an instance already we should usually use it, unless it is // no longer suitable. if (dest_instance) { - // Note: The later call to IsSuitableForURL does not have context about + // Note: The later call to IsSuitableForUrlInfo does not have context about // error page navigations, so we cannot rely on it to return correct value // when error pages are involved. if (IsSiteInstanceCompatibleWithErrorIsolation( @@ -2095,10 +2127,10 @@ dest_instance, frame_tree_node_->IsMainFrame(), dest_url_info.url, web_exposed_isolation_info, is_speculative)) { // TODO(nasko,creis): The check whether data: or about: URLs are allowed - // to commit in the current process should be in IsSuitableForURL. + // to commit in the current process should be in IsSuitableForUrlInfo. // However, making this change has further implications and needs more // investigation of what behavior changes. For now, use a conservative - // approach and explicitly check before calling IsSuitableForURL. + // approach and explicitly check before calling IsSuitableForUrlInfo. SiteInstanceImpl* dest_instance_impl = static_cast<SiteInstanceImpl*>(dest_instance); if (IsDataOrAbout(dest_url_info.url) ||
diff --git a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc index d5ae8ef..1d0791b 100644 --- a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc +++ b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
@@ -74,6 +74,7 @@ #include "content/shell/browser/shell.h" #include "content/test/content_browser_test_utils_internal.h" #include "content/test/render_document_feature.h" +#include "content/test/storage_partition_test_helpers.h" #include "content/test/test_content_browser_client.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/controllable_http_response.h" @@ -4368,6 +4369,96 @@ } } +// Verifies that a renderer-initiated navigation from a site in the default +// StoragePartition to one that ContentBrowserClient places in a non-default +// StoragePartition will swap to a new BrowsingInstance. +IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, + NavigationToDifferentPartitionSwapsBrowsingInstance) { + ASSERT_TRUE(embedded_test_server()->Start()); + + // Set up a ContentBrowserClient that maps b.com to a non-default partition. + CustomStoragePartitionForSomeSites modified_client(GURL("http://b.com/")); + ContentBrowserClient* old_client = + SetBrowserClientForTesting(&modified_client); + + // Load a page on a.com and verify that it uses the default partition. + GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); + EXPECT_TRUE(NavigateToURL(shell(), a_url)); + RenderFrameHostImpl* rfh = + static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame(); + SiteInstanceImpl* a_site_instance = rfh->GetSiteInstance(); + EXPECT_TRUE( + a_site_instance->GetSiteInfo().storage_partition_config().is_default()); + + // Make sure proactive BrowserInstance swapping doesn't interfere. + rfh->DisableProactiveBrowsingInstanceSwapForTesting(); + + // Do a renderer-initiated navigation to a page on b.com and verify that we + // swapped BrowsingInstances. + GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); + EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url)); + rfh = static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame(); + SiteInstanceImpl* b_site_instance = rfh->GetSiteInstance(); + EXPECT_FALSE( + b_site_instance->GetSiteInfo().storage_partition_config().is_default()); + EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance)); + + // Restore the original ContentBrowserClient. + SetBrowserClientForTesting(old_client); +} + +// Verifies that iframes inherit their StoragePartition, even if +// ContentBrowserClient would normally place the iframe's URL in a dedicated +// StoragePartition. +IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, + SubframeInheritsStoragePartition) { + ASSERT_TRUE(embedded_test_server()->Start()); + + // Set up a ContentBrowserClient that maps b.com to a non-default partition. + CustomStoragePartitionForSomeSites modified_client(GURL("http://b.com/")); + ContentBrowserClient* old_client = + SetBrowserClientForTesting(&modified_client); + + // Load a page on b.com to verify that it uses the correct partition. + GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); + EXPECT_TRUE(NavigateToURL(shell(), b_url)); + RenderFrameHostImpl* rfh = + static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame(); + EXPECT_FALSE(rfh->GetSiteInstance() + ->GetSiteInfo() + .storage_partition_config() + .is_default()); + + // Load a page on a.com that iframes a b.com page. + GURL a_url(embedded_test_server()->GetURL( + "a.com", "/cross_site_iframe_factory.html?a(b)")); + EXPECT_TRUE(NavigateToURL(shell(), a_url)); + rfh = static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame(); + SiteInstanceImpl* a_site_instance = rfh->GetSiteInstance(); + if (AreDefaultSiteInstancesEnabled()) { + EXPECT_TRUE(a_site_instance->IsDefaultSiteInstance()); + } else { + EXPECT_EQ("http://a.com/", a_site_instance->GetSiteURL()); + } + EXPECT_TRUE( + a_site_instance->GetSiteInfo().storage_partition_config().is_default()); + + // Verify that the iframe uses the default StoragePartition. + EXPECT_EQ(2UL, rfh->GetFramesInSubtree().size()); + SiteInstanceImpl* b_site_instance = static_cast<SiteInstanceImpl*>( + rfh->GetFramesInSubtree()[1]->GetSiteInstance()); + if (AreDefaultSiteInstancesEnabled()) { + EXPECT_TRUE(b_site_instance->IsDefaultSiteInstance()); + } else { + EXPECT_EQ("http://b.com/", b_site_instance->GetSiteURL()); + } + EXPECT_TRUE( + b_site_instance->GetSiteInfo().storage_partition_config().is_default()); + + // Restore the original ContentBrowserClient. + SetBrowserClientForTesting(old_client); +} + // Ensure that these two browser-initiated navigations: // foo.com -> about:blank -> foo.com // stay in the same SiteInstance. This isn't technically required for
diff --git a/content/browser/renderer_host/render_process_host_browsertest.cc b/content/browser/renderer_host/render_process_host_browsertest.cc index d6438e1f..18afbe89 100644 --- a/content/browser/renderer_host/render_process_host_browsertest.cc +++ b/content/browser/renderer_host/render_process_host_browsertest.cc
@@ -37,6 +37,7 @@ #include "content/shell/browser/shell_browser_context.h" #include "content/shell/browser/shell_browser_main_parts.h" #include "content/shell/browser/shell_content_browser_client.h" +#include "content/test/storage_partition_test_helpers.h" #include "content/test/test_content_browser_client.h" #include "media/base/bind_to_current_loop.h" #include "media/base/media_switches.h" @@ -486,44 +487,6 @@ // tear down. } -// Class that simulates the fact that some //content embedders (e.g. by -// overriding ChromeContentBrowserClient::GetStoragePartitionConfigForSite) can -// cause `browser_context->GetDefaultStoragePartition()` to differ from -// `browser_context->GetStoragePartition(site_instance)` even if `site_instance` -// is not for guests. -class CustomStoragePartitionForSomeSites : public TestContentBrowserClient { - public: - explicit CustomStoragePartitionForSomeSites(const GURL& site_to_isolate) - : site_to_isolate_(site_to_isolate) {} - - StoragePartitionConfig GetStoragePartitionConfigForSite( - BrowserContext* browser_context, - const GURL& site) override { - // Override for |site_to_isolate_|. - if (site == site_to_isolate_) { - return StoragePartitionConfig::Create( - browser_context, "blah_isolated_storage", "blah_isolated_storage", - false /* in_memory */); - } - - return StoragePartitionConfig::CreateDefault(browser_context); - } - - StoragePartitionId GetStoragePartitionIdForSite( - BrowserContext* browser_context, - const GURL& site) override { - if (site == site_to_isolate_) - return StoragePartitionId( - site.spec(), GetStoragePartitionConfigForSite(browser_context, site)); - return StoragePartitionId(browser_context); - } - - private: - GURL site_to_isolate_; - - DISALLOW_COPY_AND_ASSIGN(CustomStoragePartitionForSomeSites); -}; - // This test verifies that SpareRenderProcessHostManager correctly accounts // for StoragePartition differences when handing out the spare process. IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, @@ -539,7 +502,7 @@ BrowserContext* browser_context = ShellContentBrowserClient::Get()->browser_context(); scoped_refptr<SiteInstance> test_site_instance = - SiteInstance::Create(browser_context)->GetRelatedSiteInstance(test_url); + SiteInstance::CreateForURL(browser_context, test_url); StoragePartition* default_storage = browser_context->GetDefaultStoragePartition(); StoragePartition* custom_storage =
diff --git a/content/browser/renderer_host/render_process_host_unittest.cc b/content/browser/renderer_host/render_process_host_unittest.cc index 03c627e7..89e8e6d 100644 --- a/content/browser/renderer_host/render_process_host_unittest.cc +++ b/content/browser/renderer_host/render_process_host_unittest.cc
@@ -24,6 +24,7 @@ #include "content/public/test/navigation_simulator.h" #include "content/public/test/test_browser_context.h" #include "content/public/test/test_utils.h" +#include "content/test/storage_partition_test_helpers.h" #include "content/test/test_render_frame_host.h" #include "content/test/test_render_view_host.h" #include "content/test/test_web_contents.h" @@ -40,6 +41,16 @@ scoped_refptr<SiteInstanceImpl> CreateForUrl(const GURL& url) { return SiteInstanceImpl::CreateForTesting(browser_context(), url); } + + scoped_refptr<SiteInstanceImpl> CreateForServiceWorker( + const GURL& url, + bool can_reuse_process = false) { + return SiteInstanceImpl::CreateForServiceWorker( + browser_context(), + UrlInfo::CreateForTesting(url, + CreateStoragePartitionConfigForTesting()), + WebExposedIsolationInfo::CreateNonIsolated(), can_reuse_process); + } }; // Tests that guest RenderProcessHosts are not considered suitable hosts when @@ -205,9 +216,7 @@ // Gets a RenderProcessHost for an unmatched service worker. scoped_refptr<SiteInstanceImpl> sw_site_instance1 = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl, - WebExposedIsolationInfo::CreateNonIsolated()); + CreateForServiceWorker(kUrl); RenderProcessHost* sw_host1 = sw_site_instance1->GetProcess(); EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS, sw_site_instance1->GetLastProcessAssignmentOutcome()); @@ -216,9 +225,7 @@ // should not reuse the existing service worker's process. We create this // second service worker to test the "find the newest process" logic later. scoped_refptr<SiteInstanceImpl> sw_site_instance2 = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl, - WebExposedIsolationInfo::CreateNonIsolated()); + CreateForServiceWorker(kUrl); RenderProcessHost* sw_host2 = sw_site_instance2->GetProcess(); EXPECT_NE(sw_host1, sw_host2); EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS, @@ -269,9 +276,7 @@ // Gets a RenderProcessHost for an unmatched service worker. scoped_refptr<SiteInstanceImpl> sw_site_instance = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl, - WebExposedIsolationInfo::CreateNonIsolated()); + CreateForServiceWorker(kUrl); RenderProcessHost* sw_host = sw_site_instance->GetProcess(); EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS, sw_site_instance->GetLastProcessAssignmentOutcome()); @@ -305,9 +310,8 @@ // Gets a RenderProcessHost for a service worker. scoped_refptr<SiteInstanceImpl> sw_site_instance1 = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl, WebExposedIsolationInfo::CreateNonIsolated(), - /* can_reuse_process */ true); + CreateForServiceWorker(kUrl, + /*can_reuse_process=*/true); RenderProcessHost* sw_host1 = sw_site_instance1->GetProcess(); EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS, sw_site_instance1->GetLastProcessAssignmentOutcome()); @@ -318,9 +322,7 @@ // start the service worker and want to use a new process. We create this // second service worker to test the "find the newest process" logic later. scoped_refptr<SiteInstanceImpl> sw_site_instance2 = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl, - WebExposedIsolationInfo::CreateNonIsolated()); + CreateForServiceWorker(kUrl); RenderProcessHost* sw_host2 = sw_site_instance2->GetProcess(); EXPECT_NE(sw_host1, sw_host2); EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS, @@ -330,9 +332,8 @@ // REUSE_PENDING_OR_COMMITTED_SITE reuse policy should reuse the newest // unmatched service worker's process (i.e., sw_host2). scoped_refptr<SiteInstanceImpl> sw_site_instance3 = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl, WebExposedIsolationInfo::CreateNonIsolated(), - /* can_reuse_process */ true); + CreateForServiceWorker(kUrl, + /*can_reuse_process=*/true); RenderProcessHost* sw_host3 = sw_site_instance3->GetProcess(); EXPECT_EQ(sw_host2, sw_host3); EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS, @@ -344,9 +345,8 @@ // sw_host2 to be considered matched, so we can keep putting more service // workers in that process. scoped_refptr<SiteInstanceImpl> sw_site_instance4 = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl, WebExposedIsolationInfo::CreateNonIsolated(), - /* can_reuse_process */ true); + CreateForServiceWorker(kUrl, + /*can_reuse_process=*/true); RenderProcessHost* sw_host4 = sw_site_instance4->GetProcess(); EXPECT_EQ(sw_host2, sw_host4); EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS, @@ -377,9 +377,7 @@ // Gets a RenderProcessHost for a service worker with process-per-site flag. scoped_refptr<SiteInstanceImpl> sw_site_instance1 = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl, - WebExposedIsolationInfo::CreateNonIsolated()); + CreateForServiceWorker(kUrl); RenderProcessHost* sw_host1 = sw_site_instance1->GetProcess(); EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS, sw_site_instance1->GetLastProcessAssignmentOutcome()); @@ -387,9 +385,7 @@ // Getting a RenderProcessHost for a service worker of the same site with // process-per-site flag should reuse the unmatched service worker's process. scoped_refptr<SiteInstanceImpl> sw_site_instance2 = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl, - WebExposedIsolationInfo::CreateNonIsolated()); + CreateForServiceWorker(kUrl); RenderProcessHost* sw_host2 = sw_site_instance2->GetProcess(); EXPECT_EQ(sw_host1, sw_host2); EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS, @@ -418,9 +414,7 @@ // Gets a RenderProcessHost for a service worker. scoped_refptr<SiteInstanceImpl> sw_site_instance1 = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl1, - WebExposedIsolationInfo::CreateNonIsolated()); + CreateForServiceWorker(kUrl1); RenderProcessHost* sw_host1 = sw_site_instance1->GetProcess(); EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS, sw_site_instance1->GetLastProcessAssignmentOutcome()); @@ -891,9 +885,7 @@ // Create a RenderProcessHost for a service worker. scoped_refptr<SiteInstanceImpl> sw_site_instance = - SiteInstanceImpl::CreateForServiceWorker( - browser_context(), kUrl, - WebExposedIsolationInfo::CreateNonIsolated()); + CreateForServiceWorker(kUrl); RenderProcessHost* sw_process = sw_site_instance->GetProcess(); // Change foo.com SiteInstances to use a different StoragePartition.
diff --git a/content/browser/service_worker/service_worker_process_manager.cc b/content/browser/service_worker/service_worker_process_manager.cc index fd4bf22..af46643 100644 --- a/content/browser/service_worker/service_worker_process_manager.cc +++ b/content/browser/service_worker/service_worker_process_manager.cc
@@ -117,6 +117,7 @@ // Create a SiteInstance to get the renderer process from. Use the site URL // from the StoragePartition in case this StoragePartition is for guests // (e.g., <webview>). + DCHECK(storage_partition_); const bool is_guest = storage_partition_ && !storage_partition_->site_for_guest_service_worker_or_shared_worker() @@ -129,9 +130,12 @@ !is_guest && cross_origin_embedder_policy.has_value() && network::CompatibleWithCrossOriginIsolated( cross_origin_embedder_policy->value); + const UrlInfo url_info(service_worker_url, + UrlInfo::OriginIsolationRequest::kNone, + storage_partition_->GetConfig()); scoped_refptr<SiteInstanceImpl> site_instance = SiteInstanceImpl::CreateForServiceWorker( - browser_context_, service_worker_url, + browser_context_, url_info, is_coop_coep_cross_origin_isolated ? WebExposedIsolationInfo::CreateIsolated( url::Origin::Create(service_worker_url))
diff --git a/content/browser/service_worker/service_worker_process_manager_unittest.cc b/content/browser/service_worker/service_worker_process_manager_unittest.cc index 4bd09fb..df3808ed 100644 --- a/content/browser/service_worker/service_worker_process_manager_unittest.cc +++ b/content/browser/service_worker/service_worker_process_manager_unittest.cc
@@ -66,6 +66,8 @@ browser_context_ = std::make_unique<TestBrowserContext>(); process_manager_ = std::make_unique<ServiceWorkerProcessManager>(browser_context_.get()); + process_manager_->set_storage_partition(static_cast<StoragePartitionImpl*>( + browser_context_->GetDefaultStoragePartition())); script_url_ = GURL("http://www.example.com/sw.js"); render_process_host_factory_ = std::make_unique<SiteInstanceRenderProcessHostFactory>();
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc index 79974d5..b4b14f6b 100644 --- a/content/browser/site_instance_impl.cc +++ b/content/browser/site_instance_impl.cc
@@ -92,6 +92,21 @@ return GURL(kUnreachableWebDataURL); } +// Asks the embedder whether effective URLs should be used when determining if +// |dest_url| should end up in |site_instance|. +// This is used to keep same-site scripting working for hosted apps. +bool ShouldCompareEffectiveURLs(BrowserContext* browser_context, + SiteInstanceImpl* site_instance, + bool for_main_frame, + const GURL& dest_url) { + return site_instance->IsDefaultSiteInstance() || + GetContentClient() + ->browser() + ->ShouldCompareEffectiveURLsForSiteInstanceSelection( + browser_context, site_instance, for_main_frame, + site_instance->original_url(), dest_url); +} + SiteInstanceId::Generator g_site_instance_id_generator; } // namespace @@ -125,6 +140,13 @@ storage_partition_config(storage_partition_config_in) {} UrlInfo::~UrlInfo() = default; +UrlInfo UrlInfo::CreateCopyWithStoragePartitionConfig( + absl::optional<StoragePartitionConfig> storage_partition_config_in) const { + UrlInfo copy = *this; + copy.storage_partition_config = storage_partition_config_in; + return copy; +} + // static const GURL& SiteInstanceImpl::GetDefaultSiteURL() { struct DefaultSiteURL { @@ -772,15 +794,16 @@ // static scoped_refptr<SiteInstanceImpl> SiteInstanceImpl::CreateForServiceWorker( BrowserContext* browser_context, - const GURL& url, + const UrlInfo& url_info, const WebExposedIsolationInfo& web_exposed_isolation_info, bool can_reuse_process, bool is_guest) { - DCHECK(!url.SchemeIs(kChromeErrorScheme)); + DCHECK(!url_info.url.SchemeIs(kChromeErrorScheme)); + DCHECK(url_info.storage_partition_config.has_value()); scoped_refptr<SiteInstanceImpl> site_instance; if (is_guest) { - site_instance = CreateForGuest(browser_context, url); + site_instance = CreateForGuest(browser_context, url_info.url); } else { // This will create a new SiteInstance and BrowsingInstance. scoped_refptr<BrowsingInstance> instance( @@ -789,8 +812,7 @@ // We do NOT want to allow the default site instance here because workers // need to be kept separate from other sites. site_instance = instance->GetSiteInstanceForURL( - UrlInfo(url, UrlInfo::OriginIsolationRequest::kNone), - /* allow_default_instance */ false); + url_info, /* allow_default_instance */ false); } DCHECK(!site_instance->GetSiteInfo().is_error_page()); site_instance->is_for_service_worker_ = true; @@ -1483,31 +1505,20 @@ const GURL& dest_url = dest_url_info.url; BrowserContext* browser_context = GetBrowserContext(); - // Ask embedder whether effective URLs should be used when determining if - // |dest_url| should end up in this SiteInstance. - // This is used to keep same-site scripting working for hosted apps. - bool should_compare_effective_urls = - IsDefaultSiteInstance() || - GetContentClient() - ->browser() - ->ShouldCompareEffectiveURLsForSiteInstanceSelection( - browser_context, this, for_main_frame, original_url(), dest_url); + bool should_compare_effective_urls = ShouldCompareEffectiveURLs( + browser_context, this, for_main_frame, dest_url); - bool src_has_effective_url = !IsDefaultSiteInstance() && - HasEffectiveURL(browser_context, original_url()); - bool dest_has_effective_url = HasEffectiveURL(browser_context, dest_url); - - // If IsSuitableForURL finds a process type mismatch, return false + // If IsSuitableForUrlInfo finds a process type mismatch, return false // even if |dest_url| is same-site. (The URL may have been installed as an // app since the last time we visited it.) // - // This check must be skipped to keep same-site subframe navigations from a - // hosted app to non-hosted app, and vice versa, in the same process. - // Otherwise, this would return false due to a process privilege level - // mismatch. + // This check must be skipped for certain same-site navigations from a hosted + // app to non-hosted app, and vice versa, to keep them in the same process + // due to scripting requirements. Otherwise, this would return false due to + // a process privilege level mismatch. bool should_check_for_wrong_process = - should_compare_effective_urls || - (!src_has_effective_url && !dest_has_effective_url); + !IsNavigationAllowedToStayInSameProcessDueToEffectiveURLs( + browser_context, for_main_frame, dest_url); if (should_check_for_wrong_process && !IsSuitableForUrlInfo(dest_url_info)) return false; @@ -1560,6 +1571,22 @@ return false; } +bool SiteInstanceImpl::IsNavigationAllowedToStayInSameProcessDueToEffectiveURLs( + BrowserContext* browser_context, + bool for_main_frame, + const GURL& dest_url) { + if (ShouldCompareEffectiveURLs(browser_context, this, for_main_frame, + dest_url)) { + return false; + } + + bool src_has_effective_url = !IsDefaultSiteInstance() && + HasEffectiveURL(browser_context, original_url()); + if (src_has_effective_url) + return true; + return HasEffectiveURL(browser_context, dest_url); +} + // static bool SiteInstanceImpl::IsSameSite(const IsolationContext& isolation_context, const UrlInfo& real_src_url_info,
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h index 578cdf8..2180214 100644 --- a/content/browser/site_instance_impl.h +++ b/content/browser/site_instance_impl.h
@@ -106,6 +106,11 @@ return (origin_isolation_request & OriginIsolationRequest::kCOOP); } + // Creates a copy of this UrlInfo that has its |storage_partition_config| + // field set to |storage_partition_config_in|. + UrlInfo CreateCopyWithStoragePartitionConfig( + absl::optional<StoragePartitionConfig> storage_partition_config_in) const; + GURL url; // This field indicates whether the URL is requesting additional process @@ -505,7 +510,7 @@ // guest. static scoped_refptr<SiteInstanceImpl> CreateForServiceWorker( BrowserContext* browser_context, - const GURL& url, + const UrlInfo& url_info, const WebExposedIsolationInfo& web_exposed_isolation_info, bool can_reuse_process = false, bool is_guest = false); @@ -633,6 +638,18 @@ bool for_main_frame, const UrlInfo& dest_url_info); + // Returns true if a navigation to |dest_url| should be allowed to stay in + // the current process due to effective URLs being involved in the + // navigation, even if the navigation would normally result in a new process. + // + // This is needed to avoid BrowsingInstance swaps in cases where same-site + // navigations transition from a hosted app to a non-hosted app URL and must + // be kept in the same process due to scripting requirements. + bool IsNavigationAllowedToStayInSameProcessDueToEffectiveURLs( + BrowserContext* browser_context, + bool for_main_frame, + const GURL& dest_url); + // SiteInfo related functions. // Returns the SiteInfo principal identifying all documents and workers within
diff --git a/content/browser/site_instance_impl_unittest.cc b/content/browser/site_instance_impl_unittest.cc index b59986e..32e4d9ac 100644 --- a/content/browser/site_instance_impl_unittest.cc +++ b/content/browser/site_instance_impl_unittest.cc
@@ -1964,4 +1964,35 @@ ->GetSiteInfo()); } +TEST_F(SiteInstanceTest, RelatedSitesInheritStoragePartitionConfig) { + const GURL test_url("https://example.com"); + const auto isolation_info = WebExposedIsolationInfo::CreateNonIsolated(); + + // Create a UrlInfo for test_url loaded in a special StoragePartition. + const auto non_default_partition_config = + CreateStoragePartitionConfigForTesting( + /*in_memory=*/false, /*partition_domain=*/"test_partition"); + const UrlInfo partitioned_url_info(test_url, + UrlInfo::OriginIsolationRequest::kNone, + non_default_partition_config); + + // Create a SiteInstance for test_url in the special StoragePartition, and + // verify that the StoragePartition is correct. + const auto partitioned_instance = SiteInstanceImpl::CreateForUrlInfo( + context(), partitioned_url_info, isolation_info); + EXPECT_EQ(non_default_partition_config, + static_cast<SiteInstanceImpl*>(partitioned_instance.get()) + ->GetSiteInfo() + .storage_partition_config()); + + // Create a related SiteInstance that doesn't specify a + // StoragePartitionConfig and make sure the StoragePartition gets propagated. + const auto related_instance = + partitioned_instance->GetRelatedSiteInstance(test_url); + EXPECT_EQ(non_default_partition_config, + static_cast<SiteInstanceImpl*>(related_instance.get()) + ->GetSiteInfo() + .storage_partition_config()); +} + } // namespace content
diff --git a/content/browser/speech/tts_win.cc b/content/browser/speech/tts_win.cc index 03056ea9..e5f60db 100644 --- a/content/browser/speech/tts_win.cc +++ b/content/browser/speech/tts_win.cc
@@ -9,9 +9,8 @@ #include <wrl/client.h> #include <wrl/implements.h> -#include <algorithm> - #include "base/bind.h" +#include "base/cxx17_backports.h" #include "base/macros.h" #include "base/no_destructor.h" #include "base/sequenced_task_runner.h" @@ -276,7 +275,7 @@ // value to an int before calling NumberToWString. TODO(dtseng): cleanup if // we ever use any other properties that require xml. double adjusted_pitch = - std::max<double>(-10, std::min<double>(params.pitch * 10 - 10, 10)); + base::clamp<double>(params.pitch * 10 - 10, -10, 10); std::wstring adjusted_pitch_string = base::NumberToWString(static_cast<int>(adjusted_pitch)); prefix = L"<pitch absmiddle=\"" + adjusted_pitch_string + L"\">";
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc index 60b3ee2..bd09f16 100644 --- a/content/browser/storage_partition_impl.cc +++ b/content/browser/storage_partition_impl.cc
@@ -32,6 +32,7 @@ #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "components/leveldb_proto/public/proto_database_provider.h" +#include "components/services/storage/public/cpp/buckets/bucket_info.h" #include "components/services/storage/public/cpp/constants.h" #include "components/services/storage/public/cpp/filesystem/filesystem_impl.h" #include "components/services/storage/public/mojom/filesystem/directory.mojom.h" @@ -259,17 +260,17 @@ } } -void OnQuotaManagedStorageKeyDeleted(const blink::StorageKey& storage_key, - blink::mojom::StorageType type, - size_t* deletion_task_count, - base::OnceClosure callback, - blink::mojom::QuotaStatusCode status) { +void OnQuotaManagedBucketDeleted(const storage::BucketInfo& bucket, + size_t* deletion_task_count, + base::OnceClosure callback, + blink::mojom::QuotaStatusCode status) { DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_GT(*deletion_task_count, 0u); if (status != blink::mojom::QuotaStatusCode::kOk) { - DLOG(ERROR) << "Couldn't remove data of type " << static_cast<int>(type) - << " for storage key " << storage_key.GetDebugString() - << ". Status: " << static_cast<int>(status); + DLOG(ERROR) << "Couldn't remove data type " << static_cast<int>(bucket.type) + << " for bucket " << bucket.name << " with storage key " + << bucket.storage_key.GetDebugString() << " and bucket id " + << bucket.id << ". Status: " << static_cast<int>(status); } (*deletion_task_count)--; @@ -912,14 +913,14 @@ StoragePartition::OriginMatcherFunction origin_matcher, bool perform_storage_cleanup); - void ClearStorageKeysOnIOThread( + void ClearBucketsOnIOThread( storage::QuotaManager* quota_manager, const scoped_refptr<storage::SpecialStoragePolicy>& special_storage_policy, StoragePartition::OriginMatcherFunction origin_matcher, bool perform_storage_cleanup, base::OnceClosure callback, - const std::set<blink::StorageKey>& storage_keys, + const std::set<storage::BucketInfo>& buckets, blink::mojom::StorageType quota_storage_type); private: @@ -2205,60 +2206,57 @@ if (quota_storage_remove_mask_ & QUOTA_MANAGED_STORAGE_MASK_PERSISTENT) { IncrementTaskCountOnIO(); - // Ask the QuotaManager for all storage keys with persistent quota modified + // Ask the QuotaManager for all buckets with persistent quota modified // within the user-specified timeframe, and deal with the resulting set in - // ClearQuotaManagedOriginsOnIOThread(). - quota_manager->GetStorageKeysModifiedBetween( + // ClearBucketsOnIOThread(). + quota_manager->GetBucketsModifiedBetween( blink::mojom::StorageType::kPersistent, begin, end, - base::BindOnce( - &QuotaManagedDataDeletionHelper::ClearStorageKeysOnIOThread, - base::Unretained(this), base::RetainedRef(quota_manager), - special_storage_policy, origin_matcher, perform_storage_cleanup, - decrement_callback)); + base::BindOnce(&QuotaManagedDataDeletionHelper::ClearBucketsOnIOThread, + base::Unretained(this), base::RetainedRef(quota_manager), + special_storage_policy, origin_matcher, + perform_storage_cleanup, decrement_callback)); } // Do the same for temporary quota. if (quota_storage_remove_mask_ & QUOTA_MANAGED_STORAGE_MASK_TEMPORARY) { IncrementTaskCountOnIO(); - quota_manager->GetStorageKeysModifiedBetween( + quota_manager->GetBucketsModifiedBetween( blink::mojom::StorageType::kTemporary, begin, end, - base::BindOnce( - &QuotaManagedDataDeletionHelper::ClearStorageKeysOnIOThread, - base::Unretained(this), base::RetainedRef(quota_manager), - special_storage_policy, origin_matcher, perform_storage_cleanup, - decrement_callback)); + base::BindOnce(&QuotaManagedDataDeletionHelper::ClearBucketsOnIOThread, + base::Unretained(this), base::RetainedRef(quota_manager), + special_storage_policy, origin_matcher, + perform_storage_cleanup, decrement_callback)); } // Do the same for syncable quota. if (quota_storage_remove_mask_ & QUOTA_MANAGED_STORAGE_MASK_SYNCABLE) { IncrementTaskCountOnIO(); - quota_manager->GetStorageKeysModifiedBetween( + quota_manager->GetBucketsModifiedBetween( blink::mojom::StorageType::kSyncable, begin, end, - base::BindOnce( - &QuotaManagedDataDeletionHelper::ClearStorageKeysOnIOThread, - base::Unretained(this), base::RetainedRef(quota_manager), - special_storage_policy, std::move(origin_matcher), - perform_storage_cleanup, decrement_callback)); + base::BindOnce(&QuotaManagedDataDeletionHelper::ClearBucketsOnIOThread, + base::Unretained(this), base::RetainedRef(quota_manager), + special_storage_policy, std::move(origin_matcher), + perform_storage_cleanup, decrement_callback)); } DecrementTaskCountOnIO(); } void StoragePartitionImpl::QuotaManagedDataDeletionHelper:: - ClearStorageKeysOnIOThread( + ClearBucketsOnIOThread( storage::QuotaManager* quota_manager, const scoped_refptr<storage::SpecialStoragePolicy>& special_storage_policy, StoragePartition::OriginMatcherFunction origin_matcher, bool perform_storage_cleanup, base::OnceClosure callback, - const std::set<blink::StorageKey>& storage_keys, + const std::set<storage::BucketInfo>& buckets, blink::mojom::StorageType quota_storage_type) { // The QuotaManager manages all storage other than cookies, LocalStorage, // and SessionStorage. This loop wipes out most HTML5 storage for the given // storage keys. DCHECK_CURRENTLY_ON(BrowserThread::IO); - if (storage_keys.empty()) { + if (buckets.empty()) { std::move(callback).Run(); return; } @@ -2278,12 +2276,13 @@ size_t* deletion_task_count = new size_t(0u); (*deletion_task_count)++; - for (const auto& storage_key : storage_keys) { + for (const auto& bucket : buckets) { // TODO(mkwst): Clean this up, it's slow. http://crbug.com/130746 - if (storage_origin_.has_value() && storage_key.origin() != *storage_origin_) + if (storage_origin_.has_value() && + bucket.storage_key.origin() != *storage_origin_) continue; - if (origin_matcher && !origin_matcher.Run(storage_key.origin(), + if (origin_matcher && !origin_matcher.Run(bucket.storage_key.origin(), special_storage_policy.get())) { continue; } @@ -2292,11 +2291,10 @@ done_callback = std::move(split_callback.first); (*deletion_task_count)++; - quota_manager->DeleteStorageKeyData( - storage_key, quota_storage_type, quota_client_types, - base::BindOnce(&OnQuotaManagedStorageKeyDeleted, storage_key, - quota_storage_type, deletion_task_count, - std::move(split_callback.second))); + quota_manager->DeleteBucketData( + bucket, quota_client_types, + base::BindOnce(&OnQuotaManagedBucketDeleted, bucket, + deletion_task_count, std::move(split_callback.second))); } (*deletion_task_count)--;
diff --git a/content/browser/storage_partition_impl_map.cc b/content/browser/storage_partition_impl_map.cc index 7ff330d..d1803e8 100644 --- a/content/browser/storage_partition_impl_map.cc +++ b/content/browser/storage_partition_impl_map.cc
@@ -466,20 +466,18 @@ // Check first to avoid memory leak in unittests. if (BrowserThread::IsThreadInitialized(BrowserThread::IO)) { - // Use PostTask() instead of RunOrPostTaskOnThread() because not posting a - // task causes it to run before the CacheStorageManager has been - // initialized, and then CacheStorageContextImpl::CacheManager() ends up - // returning null instead of using the CrossSequenceCacheStorageManager in - // unit tests that don't use a real IO thread, violating the DCHECK in + // Use PostTask() because not posting a task causes it to run before the + // CacheStorageManager has been initialized, and then + // CacheStorageContextImpl::CacheManager() ends up returning null instead of + // using the CrossSequenceCacheStorageManager in unit tests that don't use a + // real IO thread, violating the DCHECK in // BackgroundFetchDataManager::InitializeOnCoreThread(). // TODO(crbug.com/960012): This workaround should be unnecessary after // CacheStorage moves off the IO thread to the thread pool. - BrowserThread::GetTaskRunnerForThread( - ServiceWorkerContext::GetCoreThreadId()) - ->PostTask( - FROM_HERE, - base::BindOnce(&BackgroundFetchContext::InitializeOnCoreThread, - partition->GetBackgroundFetchContext())); + BrowserThread::GetTaskRunnerForThread(BrowserThread::UI) + ->PostTask(FROM_HERE, + base::BindOnce(&BackgroundFetchContext::Initialize, + partition->GetBackgroundFetchContext())); // We do not call InitializeURLRequestContext() for media contexts because, // other than the HTTP cache, the media contexts share the same backing
diff --git a/content/browser/storage_partition_impl_unittest.cc b/content/browser/storage_partition_impl_unittest.cc index 5c34baa5..1122c23 100644 --- a/content/browser/storage_partition_impl_unittest.cc +++ b/content/browser/storage_partition_impl_unittest.cc
@@ -987,57 +987,17 @@ storage::QuotaClientType::kNativeIO)); } -void PopulateTestQuotaManagedPersistentData( +storage::BucketInfo AddQuotaManagedBucket( storage::MockQuotaManager* manager, - const blink::StorageKey& storage_key_1, - const blink::StorageKey& storage_key_2) { - manager->AddStorageKey(storage_key_1, kPersistent, {kClientFile}, - base::Time()); - manager->AddStorageKey(storage_key_2, kPersistent, {kClientFile}, - base::Time::Now() - base::TimeDelta::FromDays(1)); - - EXPECT_TRUE( - manager->StorageKeyHasData(storage_key_1, kPersistent, kClientFile)); - EXPECT_TRUE( - manager->StorageKeyHasData(storage_key_2, kPersistent, kClientFile)); -} - -void PopulateTestQuotaManagedTemporaryData( - storage::MockQuotaManager* manager, - const blink::StorageKey& storage_key_1, - const blink::StorageKey& storage_key_2) { - manager->AddStorageKey(storage_key_1, kTemporary, {kClientFile}, - base::Time::Now()); - manager->AddStorageKey(storage_key_2, kTemporary, {kClientFile}, - base::Time::Now() - base::TimeDelta::FromDays(1)); - - EXPECT_TRUE( - manager->StorageKeyHasData(storage_key_1, kTemporary, kClientFile)); - EXPECT_TRUE( - manager->StorageKeyHasData(storage_key_2, kTemporary, kClientFile)); -} - -void PopulateTestQuotaManagedData(storage::MockQuotaManager* manager, - const blink::StorageKey& storage_key_1, - const blink::StorageKey& storage_key_2, - const blink::StorageKey& storage_key_3) { - // Set up storage_key_1 with a temporary quota, storage_key_2 with a - // persistent quota, and storage_key_3 with both. storage_key_1 is modified - // now, storage_key_2 is modified at the beginning of time, and storage_key_3 - // is modified one day ago. - PopulateTestQuotaManagedTemporaryData(manager, storage_key_1, storage_key_3); - PopulateTestQuotaManagedPersistentData(manager, storage_key_2, storage_key_3); - EXPECT_FALSE( - manager->StorageKeyHasData(storage_key_1, kPersistent, kClientFile)); - EXPECT_FALSE( - manager->StorageKeyHasData(storage_key_2, kTemporary, kClientFile)); -} - -void PopulateTestQuotaManagedNonBrowsingData( const blink::StorageKey& storage_key, - storage::MockQuotaManager* manager) { - manager->AddStorageKey(storage_key, kTemporary, {kClientFile}, base::Time()); - manager->AddStorageKey(storage_key, kPersistent, {kClientFile}, base::Time()); + const std::string& bucket_name, + blink::mojom::StorageType type, + base::Time modified = base::Time::Now()) { + storage::BucketInfo bucket = + manager->CreateBucket(storage_key, bucket_name, type); + manager->AddBucket(bucket, {kClientFile}, modified); + EXPECT_TRUE(manager->BucketHasData(bucket, kClientFile)); + return bucket; } TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverBoth) { @@ -1048,8 +1008,15 @@ const blink::StorageKey kStorageKey3 = blink::StorageKey::CreateFromStringForTesting("http://host3:1/"); - PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2, - kStorageKey3); + AddQuotaManagedBucket(GetMockManager(), kStorageKey1, + storage::kDefaultBucketName, kTemporary); + AddQuotaManagedBucket(GetMockManager(), kStorageKey2, + storage::kDefaultBucketName, kTemporary); + AddQuotaManagedBucket(GetMockManager(), kStorageKey2, + storage::kDefaultBucketName, kPersistent); + AddQuotaManagedBucket(GetMockManager(), kStorageKey3, + storage::kDefaultBucketName, kPersistent); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4); StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( browser_context()->GetDefaultStoragePartition()); @@ -1060,18 +1027,7 @@ FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop)); run_loop.Run(); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent, - kClientFile)); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0); } TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverOnlyTemporary) { @@ -1080,8 +1036,11 @@ const blink::StorageKey kStorageKey2 = blink::StorageKey::CreateFromStringForTesting("http://host2:1/"); - PopulateTestQuotaManagedTemporaryData(GetMockManager(), kStorageKey1, - kStorageKey2); + AddQuotaManagedBucket(GetMockManager(), kStorageKey1, + storage::kDefaultBucketName, kTemporary); + AddQuotaManagedBucket(GetMockManager(), kStorageKey2, + storage::kDefaultBucketName, kTemporary); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2); StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( browser_context()->GetDefaultStoragePartition()); @@ -1092,14 +1051,7 @@ FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop)); run_loop.Run(); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent, - kClientFile)); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0); } TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverOnlyPersistent) { @@ -1108,8 +1060,11 @@ const blink::StorageKey kStorageKey2 = blink::StorageKey::CreateFromStringForTesting("http://host2:1/"); - PopulateTestQuotaManagedPersistentData(GetMockManager(), kStorageKey1, - kStorageKey2); + AddQuotaManagedBucket(GetMockManager(), kStorageKey1, + storage::kDefaultBucketName, kPersistent); + AddQuotaManagedBucket(GetMockManager(), kStorageKey2, + storage::kDefaultBucketName, kPersistent); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2); StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( browser_context()->GetDefaultStoragePartition()); @@ -1120,23 +1075,11 @@ FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop)); run_loop.Run(); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent, - kClientFile)); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0); } TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverNeither) { - const blink::StorageKey kStorageKey1 = - blink::StorageKey::CreateFromStringForTesting("http://host1:1/"); - const blink::StorageKey kStorageKey2 = - blink::StorageKey::CreateFromStringForTesting("http://host2:1/"); - const blink::StorageKey kStorageKey3 = - blink::StorageKey::CreateFromStringForTesting("http://host3:1/"); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0); StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( browser_context()->GetDefaultStoragePartition()); @@ -1147,18 +1090,7 @@ FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop)); run_loop.Run(); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent, - kClientFile)); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0); } TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverSpecificOrigin) { @@ -1169,8 +1101,16 @@ const blink::StorageKey kStorageKey3 = blink::StorageKey::CreateFromStringForTesting("http://host3:1/"); - PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2, - kStorageKey3); + storage::BucketInfo host1_temp_bucket = AddQuotaManagedBucket( + GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kTemporary); + storage::BucketInfo host2_temp_bucket = AddQuotaManagedBucket( + GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kTemporary); + storage::BucketInfo host2_perm_bucket = AddQuotaManagedBucket( + GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kPersistent); + storage::BucketInfo host3_perm_bucket = AddQuotaManagedBucket( + GetMockManager(), kStorageKey3, storage::kDefaultBucketName, kPersistent); + + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4); StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( browser_context()->GetDefaultStoragePartition()); @@ -1183,18 +1123,11 @@ kStorageKey1.origin().GetURL(), base::Time(), &run_loop)); run_loop.Run(); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary, - kClientFile)); - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent, - kClientFile)); - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent, - kClientFile)); - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent, - kClientFile)); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 3); + EXPECT_FALSE(GetMockManager()->BucketHasData(host1_temp_bucket, kClientFile)); + EXPECT_TRUE(GetMockManager()->BucketHasData(host2_temp_bucket, kClientFile)); + EXPECT_TRUE(GetMockManager()->BucketHasData(host2_perm_bucket, kClientFile)); + EXPECT_TRUE(GetMockManager()->BucketHasData(host3_perm_bucket, kClientFile)); } TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForLastHour) { @@ -1205,8 +1138,33 @@ const blink::StorageKey kStorageKey3 = blink::StorageKey::CreateFromStringForTesting("http://host3:1/"); - PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2, - kStorageKey3); + // Buckets modified now. + base::Time now = base::Time::Now(); + storage::BucketInfo host1_temp_bucket_now = AddQuotaManagedBucket( + GetMockManager(), kStorageKey1, "temp_bucket_now", kTemporary, now); + storage::BucketInfo host1_perm_bucket_now = AddQuotaManagedBucket( + GetMockManager(), kStorageKey1, "perm_bucket_now", kPersistent, now); + storage::BucketInfo host2_temp_bucket_now = AddQuotaManagedBucket( + GetMockManager(), kStorageKey2, "temp_bucket_now", kTemporary, now); + storage::BucketInfo host2_perm_bucket_now = AddQuotaManagedBucket( + GetMockManager(), kStorageKey2, "perm_bucket_now", kPersistent, now); + + // Buckets modified a day ago. + base::Time yesterday = now - base::TimeDelta::FromDays(1); + storage::BucketInfo host1_temp_bucket_yesterday = + AddQuotaManagedBucket(GetMockManager(), kStorageKey1, + "temp_bucket_yesterday", kTemporary, yesterday); + storage::BucketInfo host1_perm_bucket_yesterday = + AddQuotaManagedBucket(GetMockManager(), kStorageKey1, + "perm_bucket_yesterday", kPersistent, yesterday); + storage::BucketInfo host2_temp_bucket_yesterday = + AddQuotaManagedBucket(GetMockManager(), kStorageKey2, + "temp_bucket_yesterday", kTemporary, yesterday); + storage::BucketInfo host2_perm_bucket_yesterday = + AddQuotaManagedBucket(GetMockManager(), kStorageKey2, + "perm_bucket_yesterday", kPersistent, yesterday); + + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 8); StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( browser_context()->GetDefaultStoragePartition()); @@ -1220,35 +1178,56 @@ &run_loop)); run_loop.Run(); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary, - kClientFile)); - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent, - kClientFile)); - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent, - kClientFile)); - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent, - kClientFile)); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4); + EXPECT_FALSE( + GetMockManager()->BucketHasData(host1_temp_bucket_now, kClientFile)); + EXPECT_FALSE( + GetMockManager()->BucketHasData(host1_perm_bucket_now, kClientFile)); + EXPECT_FALSE( + GetMockManager()->BucketHasData(host2_temp_bucket_now, kClientFile)); + EXPECT_FALSE( + GetMockManager()->BucketHasData(host2_perm_bucket_now, kClientFile)); + EXPECT_TRUE(GetMockManager()->BucketHasData(host1_temp_bucket_yesterday, + kClientFile)); + EXPECT_TRUE(GetMockManager()->BucketHasData(host1_perm_bucket_yesterday, + kClientFile)); + EXPECT_TRUE(GetMockManager()->BucketHasData(host2_temp_bucket_yesterday, + kClientFile)); + EXPECT_TRUE(GetMockManager()->BucketHasData(host2_perm_bucket_yesterday, + kClientFile)); } -TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForLastWeek) { - const blink::StorageKey kStorageKey1 = +TEST_F(StoragePartitionImplTest, + RemoveQuotaManagedNonPersistentDataForLastWeek) { + const blink::StorageKey kStorageKey = blink::StorageKey::CreateFromStringForTesting("http://host1:1/"); - const blink::StorageKey kStorageKey2 = - blink::StorageKey::CreateFromStringForTesting("http://host2:1/"); - const blink::StorageKey kStorageKey3 = - blink::StorageKey::CreateFromStringForTesting("http://host3:1/"); - PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2, - kStorageKey3); + // Buckets modified yesterday. + base::Time now = base::Time::Now(); + base::Time yesterday = now - base::TimeDelta::FromDays(1); + storage::BucketInfo temp_bucket_yesterday = + AddQuotaManagedBucket(GetMockManager(), kStorageKey, + "temp_bucket_yesterday", kTemporary, yesterday); + storage::BucketInfo perm_bucket_yesterday = + AddQuotaManagedBucket(GetMockManager(), kStorageKey, + "perm_bucket_yesterday", kPersistent, yesterday); + + // Buckets modified 10 days ago. + base::Time ten_days_ago = now - base::TimeDelta::FromDays(10); + storage::BucketInfo temp_bucket_ten_days_ago = AddQuotaManagedBucket( + GetMockManager(), kStorageKey, "temp_bucket_ten_days_ago", kTemporary, + ten_days_ago); + storage::BucketInfo perm_bucket_ten_days_ago = AddQuotaManagedBucket( + GetMockManager(), kStorageKey, "perm_bucket_ten_days_ago", kPersistent, + ten_days_ago); + + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4); base::RunLoop run_loop; StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( browser_context()->GetDefaultStoragePartition()); partition->OverrideQuotaManagerForTesting(GetMockManager()); + base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&ClearQuotaDataForNonPersistent, partition, @@ -1256,18 +1235,15 @@ &run_loop)); run_loop.Run(); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent, - kClientFile)); - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent, - kClientFile)); - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent, - kClientFile)); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 3); + EXPECT_FALSE( + GetMockManager()->BucketHasData(temp_bucket_yesterday, kClientFile)); + EXPECT_TRUE( + GetMockManager()->BucketHasData(perm_bucket_yesterday, kClientFile)); + EXPECT_TRUE( + GetMockManager()->BucketHasData(temp_bucket_ten_days_ago, kClientFile)); + EXPECT_TRUE( + GetMockManager()->BucketHasData(perm_bucket_ten_days_ago, kClientFile)); } TEST_F(StoragePartitionImplTest, RemoveQuotaManagedUnprotectedOrigins) { @@ -1275,16 +1251,22 @@ blink::StorageKey::CreateFromStringForTesting("http://host1:1/"); const blink::StorageKey kStorageKey2 = blink::StorageKey::CreateFromStringForTesting("http://host2:1/"); - const blink::StorageKey kStorageKey3 = - blink::StorageKey::CreateFromStringForTesting("http://host3:1/"); + + storage::BucketInfo host1_temp_bucket = AddQuotaManagedBucket( + GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kTemporary); + storage::BucketInfo host1_perm_bucket = AddQuotaManagedBucket( + GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kPersistent); + storage::BucketInfo host2_temp_bucket = AddQuotaManagedBucket( + GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kTemporary); + storage::BucketInfo host2_perm_bucket = AddQuotaManagedBucket( + GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kPersistent); + + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4); // Protect kStorageKey1. auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>(); mock_policy->AddProtected(kStorageKey1.origin().GetURL()); - PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2, - kStorageKey3); - StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( browser_context()->GetDefaultStoragePartition()); partition->OverrideQuotaManagerForTesting(GetMockManager()); @@ -1298,18 +1280,11 @@ base::Time(), &run_loop)); run_loop.Run(); - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent, - kClientFile)); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2); + EXPECT_TRUE(GetMockManager()->BucketHasData(host1_temp_bucket, kClientFile)); + EXPECT_TRUE(GetMockManager()->BucketHasData(host1_perm_bucket, kClientFile)); + EXPECT_FALSE(GetMockManager()->BucketHasData(host2_temp_bucket, kClientFile)); + EXPECT_FALSE(GetMockManager()->BucketHasData(host2_perm_bucket, kClientFile)); } TEST_F(StoragePartitionImplTest, RemoveQuotaManagedProtectedOrigins) { @@ -1317,22 +1292,28 @@ blink::StorageKey::CreateFromStringForTesting("http://host1:1/"); const blink::StorageKey kStorageKey2 = blink::StorageKey::CreateFromStringForTesting("http://host2:1/"); - const blink::StorageKey kStorageKey3 = - blink::StorageKey::CreateFromStringForTesting("http://host3:1/"); + + AddQuotaManagedBucket(GetMockManager(), kStorageKey1, + storage::kDefaultBucketName, kTemporary); + AddQuotaManagedBucket(GetMockManager(), kStorageKey1, + storage::kDefaultBucketName, kPersistent); + AddQuotaManagedBucket(GetMockManager(), kStorageKey2, + storage::kDefaultBucketName, kTemporary); + AddQuotaManagedBucket(GetMockManager(), kStorageKey2, + storage::kDefaultBucketName, kPersistent); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4); // Protect kStorageKey1. auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>(); mock_policy->AddProtected(kStorageKey1.origin().GetURL()); - PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2, - kStorageKey3); - // Try to remove kStorageKey1. Expect success. base::RunLoop run_loop; StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( browser_context()->GetDefaultStoragePartition()); partition->OverrideQuotaManagerForTesting(GetMockManager()); partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get()); + base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition, @@ -1341,18 +1322,7 @@ base::Time(), &run_loop)); run_loop.Run(); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent, - kClientFile)); - EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent, - kClientFile)); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0); } TEST_F(StoragePartitionImplTest, RemoveQuotaManagedIgnoreDevTools) { @@ -1360,12 +1330,19 @@ blink::StorageKey::CreateFromStringForTesting( "devtools://abcdefghijklmnopqrstuvw/"); - PopulateTestQuotaManagedNonBrowsingData(kStorageKey, GetMockManager()); + storage::BucketInfo temp_bucket = AddQuotaManagedBucket( + GetMockManager(), kStorageKey, storage::kDefaultBucketName, kTemporary, + base::Time()); + storage::BucketInfo perm_bucket = AddQuotaManagedBucket( + GetMockManager(), kStorageKey, storage::kDefaultBucketName, kPersistent, + base::Time()); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2); base::RunLoop run_loop; StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( browser_context()->GetDefaultStoragePartition()); partition->OverrideQuotaManagerForTesting(GetMockManager()); + base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition, base::BindRepeating(&DoesOriginMatchUnprotected, @@ -1374,10 +1351,9 @@ run_loop.Run(); // Check that devtools data isn't removed. - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey, kTemporary, - kClientFile)); - EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey, kPersistent, - kClientFile)); + EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2); + EXPECT_TRUE(GetMockManager()->BucketHasData(temp_bucket, kClientFile)); + EXPECT_TRUE(GetMockManager()->BucketHasData(perm_bucket, kClientFile)); } TEST_F(StoragePartitionImplTest, RemoveCookieForever) {
diff --git a/content/browser/web_contents/web_contents_impl_unittest.cc b/content/browser/web_contents/web_contents_impl_unittest.cc index b430d4e3..51c924f 100644 --- a/content/browser/web_contents/web_contents_impl_unittest.cc +++ b/content/browser/web_contents/web_contents_impl_unittest.cc
@@ -1333,9 +1333,9 @@ webui_rfh = contents()->GetSpeculativePrimaryMainFrame(); // DidNavigate from the second back. - // Note that the process in instance1 is gone at this point, but we will still - // use instance1 and entry1 because IsSuitableForURL will return true when - // there is no process and the site URL matches. + // Note that the process in instance1 is gone at this point, but we will + // still use instance1 and entry1 because IsSuitableForUrlInfo will return + // true when there is no process and the site URL matches. back_navigation2->Commit(); // That should have landed us on the first entry.
diff --git a/content/browser/worker_host/shared_worker_service_impl.cc b/content/browser/worker_host/shared_worker_service_impl.cc index 8979c43..669f4ff 100644 --- a/content/browser/worker_host/shared_worker_service_impl.cc +++ b/content/browser/worker_host/shared_worker_service_impl.cc
@@ -304,7 +304,8 @@ } else { site_instance = SiteInstanceImpl::CreateForUrlInfo( partition->browser_context(), - UrlInfo(instance.url(), UrlInfo::OriginIsolationRequest::kNone), + UrlInfo(instance.url(), UrlInfo::OriginIsolationRequest::kNone, + partition->GetConfig()), WebExposedIsolationInfo::CreateNonIsolated()); } }
diff --git a/content/test/data/gpu/webgpu-iframe-removed.html b/content/test/data/gpu/webgpu-iframe-removed.html index 2c38f5d..8823648 100644 --- a/content/test/data/gpu/webgpu-iframe-removed.html +++ b/content/test/data/gpu/webgpu-iframe-removed.html
@@ -178,8 +178,8 @@ parent.removeIframe(); }); - // Test creating a mappable buffer and then removing the iframe while - // the mapping is in flight. + // Test creating a mappable buffer and then in the next task, removing the + // iframe while the mapping is in flight. // Mappable buffers are tracked because mappings need to be detached // when the context is destroyed. await runTestCase(async () => { @@ -203,6 +203,30 @@ }, 0); }); + // Test creating a mappable buffer and then in the same task, removing the + // iframe while the mapping is in flight. + // Mappable buffers are tracked because mappings need to be detached + // when the context is destroyed. + await runTestCase(async () => { + const adapter = navigator.gpu && await navigator.gpu.requestAdapter(); + if (!adapter) { + console.warn('WebGPU not supported'); + parent.removeIframe(); + return; + } + const device = await adapter.requestDevice(); + + const buffer = device.createBuffer({ + size: 4, + usage: GPUBufferUsage.MAP_READ, + }); + + buffer.mapAsync(GPUMapMode.READ); + /* Empty submit to force a flush */ + device.queue.submit([]); + parent.removeIframe(); + }); + // Test removing the iframe while the buffer is mapped. // Mappable buffers are tracked because mappings need to be detached // when the context is destroyed.
diff --git a/content/test/storage_partition_test_helpers.cc b/content/test/storage_partition_test_helpers.cc index e2c3aad..ea61145c 100644 --- a/content/test/storage_partition_test_helpers.cc +++ b/content/test/storage_partition_test_helpers.cc
@@ -36,4 +36,32 @@ return StoragePartitionConfig(partition_domain, partition_name, in_memory); } +CustomStoragePartitionForSomeSites::CustomStoragePartitionForSomeSites( + const GURL& site_to_isolate) + : site_to_isolate_(site_to_isolate) {} + +StoragePartitionConfig +CustomStoragePartitionForSomeSites::GetStoragePartitionConfigForSite( + BrowserContext* browser_context, + const GURL& site) { + // Override for |site_to_isolate_|. + if (site == site_to_isolate_) { + return StoragePartitionConfig::Create( + browser_context, "blah_isolated_storage", "blah_isolated_storage", + false /* in_memory */); + } + + return StoragePartitionConfig::CreateDefault(browser_context); +} + +StoragePartitionId +CustomStoragePartitionForSomeSites::GetStoragePartitionIdForSite( + BrowserContext* browser_context, + const GURL& site) { + if (site == site_to_isolate_) + return StoragePartitionId( + site.spec(), GetStoragePartitionConfigForSite(browser_context, site)); + return StoragePartitionId(browser_context); +} + } // namespace content
diff --git a/content/test/storage_partition_test_helpers.h b/content/test/storage_partition_test_helpers.h index db566bb..79050ce 100644 --- a/content/test/storage_partition_test_helpers.h +++ b/content/test/storage_partition_test_helpers.h
@@ -6,6 +6,7 @@ #define CONTENT_TEST_STORAGE_PARTITION_TEST_HELPERS_H_ #include "base/callback.h" +#include "content/test/test_content_browser_client.h" namespace content { class StoragePartition; @@ -26,6 +27,24 @@ const std::string& partition_domain = "", const std::string& partition_name = ""); +// Class that requests that all pages belonging to the provided site get loaded +// in a non-default StoragePartition. +class CustomStoragePartitionForSomeSites : public TestContentBrowserClient { + public: + explicit CustomStoragePartitionForSomeSites(const GURL& site_to_isolate); + + StoragePartitionConfig GetStoragePartitionConfigForSite( + BrowserContext* browser_context, + const GURL& site) override; + + StoragePartitionId GetStoragePartitionIdForSite( + BrowserContext* browser_context, + const GURL& site) override; + + private: + GURL site_to_isolate_; +}; + } // namespace content #endif // CONTENT_TEST_STORAGE_PARTITION_TEST_HELPERS_H_
diff --git a/device/base/features.cc b/device/base/features.cc index 59506ef..cd871b3 100644 --- a/device/base/features.cc +++ b/device/base/features.cc
@@ -14,6 +14,11 @@ #endif // defined(OS_MAC) #if defined(OS_WIN) +const base::Feature kNewUsbBackend{"NewUsbBackend", + base::FEATURE_ENABLED_BY_DEFAULT}; +#endif // defined(OS_WIN) + +#if defined(OS_WIN) const base::Feature kNewBLEWinImplementation{"NewBLEWinImplementation", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/device/base/features.h b/device/base/features.h index 4105f3c..80bf44d 100644 --- a/device/base/features.h +++ b/device/base/features.h
@@ -12,9 +12,9 @@ namespace device { -#if defined(OS_MAC) +#if defined(OS_WIN) || defined(OS_MAC) DEVICE_BASE_EXPORT extern const base::Feature kNewUsbBackend; -#endif // defined(OS_MAC) +#endif // defined(OS_WIN) || defined(OS_MAC) #if defined(OS_WIN) DEVICE_BASE_EXPORT extern const base::Feature kNewBLEWinImplementation;
diff --git a/device/gamepad/xbox_controller_mac.mm b/device/gamepad/xbox_controller_mac.mm index 823e2f7..bac7db8 100644 --- a/device/gamepad/xbox_controller_mac.mm +++ b/device/gamepad/xbox_controller_mac.mm
@@ -18,6 +18,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" #include "base/check_op.h" +#include "base/cxx17_backports.h" #include "base/location.h" #include "base/mac/foundation_util.h" #include "base/mac/scoped_ioobject.h" @@ -337,9 +338,8 @@ return; // Clamp magnitudes to [0,1] - strong_magnitude = - std::max<double>(0.0, std::min<double>(strong_magnitude, 1.0)); - weak_magnitude = std::max<double>(0.0, std::min<double>(weak_magnitude, 1.0)); + strong_magnitude = base::clamp<double>(strong_magnitude, 0.0, 1.0); + weak_magnitude = base::clamp<double>(weak_magnitude, 0.0, 1.0); if (xinput_type_ == kXInputTypeXbox360) { WriteXbox360Rumble(static_cast<uint8_t>(strong_magnitude * 255.0),
diff --git a/gpu/command_buffer/client/dawn_client_memory_transfer_service.cc b/gpu/command_buffer/client/dawn_client_memory_transfer_service.cc index 4695ce1..bdbacb8 100644 --- a/gpu/command_buffer/client/dawn_client_memory_transfer_service.cc +++ b/gpu/command_buffer/client/dawn_client_memory_transfer_service.cc
@@ -125,7 +125,7 @@ void* DawnClientMemoryTransferService::AllocateHandle( size_t size, MemoryTransferHandle* handle) { - if (size > std::numeric_limits<uint32_t>::max()) { + if (size > std::numeric_limits<uint32_t>::max() || disconnected_) { return nullptr; } @@ -155,5 +155,9 @@ } } +void DawnClientMemoryTransferService::Disconnect() { + disconnected_ = true; +} + } // namespace webgpu } // namespace gpu
diff --git a/gpu/command_buffer/client/dawn_client_memory_transfer_service.h b/gpu/command_buffer/client/dawn_client_memory_transfer_service.h index 61efd12..3c8780cd 100644 --- a/gpu/command_buffer/client/dawn_client_memory_transfer_service.h +++ b/gpu/command_buffer/client/dawn_client_memory_transfer_service.h
@@ -17,7 +17,7 @@ struct MemoryTransferHandle; -class DawnClientMemoryTransferService final +class DawnClientMemoryTransferService : public dawn_wire::client::MemoryTransferService { public: DawnClientMemoryTransferService(MappedMemoryManager* mapped_memory); @@ -35,6 +35,8 @@ // process. void FreeHandles(CommandBufferHelper* helper); + void Disconnect(); + private: class ReadHandleImpl; class WriteHandleImpl; @@ -50,6 +52,9 @@ // Pointers to memory allocated by the MappedMemoryManager to free after // the next Flush. std::vector<void*> free_blocks_; + + // If disconnected, new handle creation always returns null. + bool disconnected_ = false; }; } // namespace webgpu
diff --git a/gpu/command_buffer/client/dawn_client_serializer.cc b/gpu/command_buffer/client/dawn_client_serializer.cc index e5d1abe..31154e2 100644 --- a/gpu/command_buffer/client/dawn_client_serializer.cc +++ b/gpu/command_buffer/client/dawn_client_serializer.cc
@@ -14,37 +14,16 @@ namespace gpu { namespace webgpu { -// static -std::unique_ptr<DawnClientSerializer> DawnClientSerializer::Create( - WebGPUImplementation* client, - WebGPUCmdHelper* helper, - DawnClientMemoryTransferService* memory_transfer_service, - const SharedMemoryLimits& limits) { - std::unique_ptr<TransferBuffer> transfer_buffer = - std::make_unique<TransferBuffer>(helper); - if (!transfer_buffer->Initialize(limits.start_transfer_buffer_size, - /* start offset */ 0, - limits.min_transfer_buffer_size, - limits.max_transfer_buffer_size, - /* alignment */ 8)) { - return nullptr; - } - return std::make_unique<DawnClientSerializer>( - client, helper, memory_transfer_service, std::move(transfer_buffer), - limits.start_transfer_buffer_size); -} - DawnClientSerializer::DawnClientSerializer( WebGPUImplementation* client, WebGPUCmdHelper* helper, DawnClientMemoryTransferService* memory_transfer_service_, - std::unique_ptr<TransferBuffer> transfer_buffer, - uint32_t buffer_initial_size) + std::unique_ptr<TransferBuffer> transfer_buffer) : client_(client), helper_(helper), memory_transfer_service_(memory_transfer_service_), transfer_buffer_(std::move(transfer_buffer)), - buffer_initial_size_(buffer_initial_size), + buffer_initial_size_(transfer_buffer_->GetSize()), buffer_(helper_, transfer_buffer_.get()) { DCHECK_GT(buffer_initial_size_, 0u); } @@ -131,7 +110,16 @@ void DawnClientSerializer::Disconnect() { buffer_.Discard(); - transfer_buffer_ = nullptr; + if (transfer_buffer_) { + auto transfer_buffer = std::move(transfer_buffer_); + // Wait for commands to finish before we free shared memory that + // the GPU process is using. + // TODO(crbug.com/1231599): This Finish may not be necessary if the + // shared memory is not immediately freed. Investigate this and + // consider optimization. + helper_->Finish(); + transfer_buffer = nullptr; + } } bool DawnClientSerializer::Flush() {
diff --git a/gpu/command_buffer/client/dawn_client_serializer.h b/gpu/command_buffer/client/dawn_client_serializer.h index 65ca6345..980440b 100644 --- a/gpu/command_buffer/client/dawn_client_serializer.h +++ b/gpu/command_buffer/client/dawn_client_serializer.h
@@ -13,7 +13,6 @@ namespace gpu { -struct SharedMemoryLimits; class TransferBuffer; namespace webgpu { @@ -22,19 +21,12 @@ class WebGPUCmdHelper; class WebGPUImplementation; -class DawnClientSerializer final : public dawn_wire::CommandSerializer { +class DawnClientSerializer : public dawn_wire::CommandSerializer { public: - static std::unique_ptr<DawnClientSerializer> Create( - WebGPUImplementation* client, - WebGPUCmdHelper* helper, - DawnClientMemoryTransferService* memory_transfer_service, - const SharedMemoryLimits& limits); - DawnClientSerializer(WebGPUImplementation* client, WebGPUCmdHelper* helper, DawnClientMemoryTransferService* memory_transfer_service, - std::unique_ptr<TransferBuffer> transfer_buffer, - uint32_t buffer_initial_size); + std::unique_ptr<TransferBuffer> transfer_buffer); ~DawnClientSerializer() override; // dawn_wire::CommandSerializer implementation
diff --git a/gpu/command_buffer/client/webgpu_cmd_helper_autogen.h b/gpu/command_buffer/client/webgpu_cmd_helper_autogen.h index 2758247..e17e061 100644 --- a/gpu/command_buffer/client/webgpu_cmd_helper_autogen.h +++ b/gpu/command_buffer/client/webgpu_cmd_helper_autogen.h
@@ -67,11 +67,4 @@ } } -void DestroyServer() { - webgpu::cmds::DestroyServer* c = GetCmdSpace<webgpu::cmds::DestroyServer>(); - if (c) { - c->Init(); - } -} - #endif // GPU_COMMAND_BUFFER_CLIENT_WEBGPU_CMD_HELPER_AUTOGEN_H_
diff --git a/gpu/command_buffer/client/webgpu_implementation.cc b/gpu/command_buffer/client/webgpu_implementation.cc index a155f47f..f5ee3c1a 100644 --- a/gpu/command_buffer/client/webgpu_implementation.cc +++ b/gpu/command_buffer/client/webgpu_implementation.cc
@@ -22,6 +22,58 @@ namespace gpu { namespace webgpu { +#if BUILDFLAG(USE_DAWN) +class DawnWireServices : public APIChannel { + private: + friend class base::RefCounted<DawnWireServices>; + ~DawnWireServices() override = default; + + public: + DawnWireServices(WebGPUImplementation* webgpu_implementation, + WebGPUCmdHelper* helper, + MappedMemoryManager* mapped_memory, + std::unique_ptr<TransferBuffer> transfer_buffer) + : memory_transfer_service_(mapped_memory), + serializer_(webgpu_implementation, + helper, + &memory_transfer_service_, + std::move(transfer_buffer)), + wire_client_(dawn_wire::WireClientDescriptor{ + &serializer_, + &memory_transfer_service_, + }) {} + + const DawnProcTable& GetProcs() const override { + return dawn_wire::client::GetProcs(); + } + + dawn_wire::WireClient* wire_client() { return &wire_client_; } + DawnClientSerializer* serializer() { return &serializer_; } + DawnClientMemoryTransferService* memory_transfer_service() { + return &memory_transfer_service_; + } + + void Disconnect() override { + disconnected_ = true; + wire_client_.Disconnect(); + serializer_.Disconnect(); + memory_transfer_service_.Disconnect(); + } + + bool IsDisconnected() const { return disconnected_; } + + void FreeMappedResources(WebGPUCmdHelper* helper) { + memory_transfer_service_.FreeHandles(helper); + } + + private: + bool disconnected_ = false; + DawnClientMemoryTransferService memory_transfer_service_; + DawnClientSerializer serializer_; + dawn_wire::WireClient wire_client_; +}; +#endif + // Include the auto-generated part of this file. We split this because it means // we can easily edit the non-auto generated parts right here in this file // instead of having to edit some template or the code generator. @@ -35,26 +87,42 @@ helper_(helper) {} WebGPUImplementation::~WebGPUImplementation() { + LoseContext(); + + // Before destroying WebGPUImplementation, all mappable buffers + // must be destroyed first. This means that all shared memory mappings are + // detached. If they are not destroyed, MappedMemoryManager (member of + // base class ImplementationBase) will assert on destruction that some + // memory blocks are in use. Calling |FreeMappedResources| marks all + // blocks that are no longer in use as free. #if BUILDFLAG(USE_DAWN) - if (!wire_client_) { - // Initialization failed. - return; + dawn_wire_->FreeMappedResources(helper_); +#endif + + // Wait for commands to finish before we continue destruction. + // WebGPUImplementation no longer owns the WebGPU transfer buffer, but still + // owns the GPU command buffer. We should not free shared memory that the + // GPU process is using. + helper_->Finish(); +} + +void WebGPUImplementation::LoseContext() { + lost_ = true; +#if BUILDFLAG(USE_DAWN) + dawn_wire_->Disconnect(); + + auto request_adapter_callback_map = std::move(request_adapter_callback_map_); + auto request_device_callback_map = std::move(request_device_callback_map_); + for (auto& it : request_adapter_callback_map) { + std::move(it.second).Run(-1, {}, "Context Lost"); + } + for (auto& it : request_device_callback_map) { + std::move(it.second).Run(false); } - // Commit all commands and synchronously wait for them to finish. - // TODO(enga): Investigate if we can just Disconnect() instead. - wire_serializer_->Commit(); - helper_->Finish(); - - // Now that commands are finished, free the wire and serializers. - wire_client_.reset(); - wire_serializer_.reset(); - - // All client-side Dawn objects are now destroyed. - // Shared memory allocations for buffers that were still mapped at the time - // of destruction can now be safely freed. - memory_transfer_service_->FreeHandles(helper_); - helper_->Finish(); + // After |lost_| is set to true, callbacks should not be enqueued anymore. + DCHECK(request_adapter_callback_map_.empty()); + DCHECK(request_device_callback_map_.empty()); #endif } @@ -66,17 +134,19 @@ return result; } + std::unique_ptr<TransferBuffer> transfer_buffer = + std::make_unique<TransferBuffer>(helper_); + if (!transfer_buffer->Initialize(limits.start_transfer_buffer_size, + /* start offset */ 0, + limits.min_transfer_buffer_size, + limits.max_transfer_buffer_size, + /* alignment */ 8)) { + return gpu::ContextResult::kFatalFailure; + } + #if BUILDFLAG(USE_DAWN) - memory_transfer_service_ = - std::make_unique<DawnClientMemoryTransferService>(mapped_memory_.get()); - - wire_serializer_ = DawnClientSerializer::Create( - this, helper_, memory_transfer_service_.get(), limits); - - dawn_wire::WireClientDescriptor descriptor = {}; - descriptor.serializer = wire_serializer_.get(); - descriptor.memoryTransferService = memory_transfer_service_.get(); - wire_client_ = std::make_unique<dawn_wire::WireClient>(descriptor); + dawn_wire_ = base::MakeRefCounted<DawnWireServices>( + this, helper_, mapped_memory_.get(), std::move(transfer_buffer)); // TODO(senorblanco): Do this only once per process. Doing it once per // WebGPUImplementation is non-optimal but valid, since the returned @@ -193,7 +263,7 @@ // Need to commit the commands to the GPU command buffer first for SyncToken // to work. #if BUILDFLAG(USE_DAWN) - wire_serializer_->Commit(); + dawn_wire_->serializer()->Commit(); #endif ImplementationBase::GenSyncToken(sync_token); } @@ -201,7 +271,7 @@ // Need to commit the commands to the GPU command buffer first for SyncToken // to work. #if BUILDFLAG(USE_DAWN) - wire_serializer_->Commit(); + dawn_wire_->serializer()->Commit(); #endif ImplementationBase::GenUnverifiedSyncToken(sync_token); } @@ -213,7 +283,7 @@ // Need to commit the commands to the GPU command buffer first for SyncToken // to work. #if BUILDFLAG(USE_DAWN) - wire_serializer_->Commit(); + dawn_wire_->serializer()->Commit(); #endif ImplementationBase::WaitSyncToken(sync_token); } @@ -240,9 +310,8 @@ } // GpuControlClient implementation. -// TODO(jiawei.shao@intel.com): do other clean-ups when the context is lost. void WebGPUImplementation::OnGpuControlLostContext() { - OnGpuControlLostContextMaybeReentrant(); + LoseContext(); // This should never occur more than once. DCHECK(!lost_context_callback_run_); @@ -252,11 +321,12 @@ } } void WebGPUImplementation::OnGpuControlLostContextMaybeReentrant() { + // If this function is called, we are guaranteed to also get a call + // to |OnGpuControlLostContext| when the callstack unwinds. Thus, this + // function only handles immediately setting state so that other operations + // which occur while the callstack is unwinding are aware that the context + // is lost. lost_ = true; -#if BUILDFLAG(USE_DAWN) - wire_client_->Disconnect(); - wire_serializer_->Disconnect(); -#endif } void WebGPUImplementation::OnGpuControlErrorMessage(const char* message, int32_t id) { @@ -299,10 +369,13 @@ const cmds::DawnReturnCommandsInfo* dawn_return_commands_info = reinterpret_cast<const cmds::DawnReturnCommandsInfo*>(data.data()); + if (dawn_wire_->IsDisconnected()) { + break; + } // TODO(enga): Instead of a CHECK, this could generate a device lost // event on just that device. It doesn't seem worth doing right now // since a failure here is likely not recoverable. - CHECK(wire_client_->HandleCommands( + CHECK(dawn_wire_->wire_client()->HandleCommands( reinterpret_cast<const char*>( dawn_return_commands_info->deserialized_buffer), data.size() - @@ -372,19 +445,9 @@ #endif } -const DawnProcTable& WebGPUImplementation::GetProcs() const { -#if !BUILDFLAG(USE_DAWN) - NOTREACHED(); - static DawnProcTable null_procs = {}; - return null_procs; -#else - return dawn_wire::client::GetProcs(); -#endif -} - void WebGPUImplementation::FlushCommands() { #if BUILDFLAG(USE_DAWN) - wire_serializer_->Commit(); + dawn_wire_->serializer()->Commit(); helper_->Flush(); #endif } @@ -394,15 +457,15 @@ // If there is already a flush waiting, we don't need to flush. // We only want to set |needs_flush| on state transition from // false -> true. - if (wire_serializer_->AwaitingFlush()) { + if (dawn_wire_->serializer()->AwaitingFlush()) { *needs_flush = false; return; } // Set the state to waiting for flush, and then write |needs_flush|. // Could still be false if there's no data to flush. - wire_serializer_->SetAwaitingFlush(true); - *needs_flush = wire_serializer_->AwaitingFlush(); + dawn_wire_->serializer()->SetAwaitingFlush(true); + *needs_flush = dawn_wire_->serializer()->AwaitingFlush(); #else *needs_flush = false; #endif @@ -410,21 +473,18 @@ void WebGPUImplementation::FlushAwaitingCommands() { #if BUILDFLAG(USE_DAWN) - if (wire_serializer_->AwaitingFlush()) { - wire_serializer_->Commit(); + if (dawn_wire_->serializer()->AwaitingFlush()) { + dawn_wire_->serializer()->Commit(); helper_->Flush(); } #endif } -void WebGPUImplementation::DisconnectContextAndDestroyServer() { - // Treat this like a context lost since the context is no longer usable. - OnGpuControlLostContextMaybeReentrant(); - +scoped_refptr<APIChannel> WebGPUImplementation::GetAPIChannel() const { #if BUILDFLAG(USE_DAWN) - // Send a message to eagerly free server-side resources. - helper_->DestroyServer(); - helper_->Flush(); + return dawn_wire_.get(); +#else + return nullptr; #endif } @@ -432,9 +492,9 @@ #if BUILDFLAG(USE_DAWN) // Commit because we need to make sure messages that free a previously used // texture are seen first. ReserveTexture may reuse an existing ID. - wire_serializer_->Commit(); + dawn_wire_->serializer()->Commit(); - auto reservation = wire_client_->ReserveTexture(device); + auto reservation = dawn_wire_->wire_client()->ReserveTexture(device); ReservedTexture result; result.texture = reservation.texture; result.id = reservation.id; @@ -512,22 +572,23 @@ // Commit because we need to make sure messages that free a previously used // device are seen first. ReserveDevice may reuse an existing ID. - wire_serializer_->Commit(); + dawn_wire_->serializer()->Commit(); - dawn_wire::ReservedDevice reservation = wire_client_->ReserveDevice(); + dawn_wire::ReservedDevice reservation = + dawn_wire_->wire_client()->ReserveDevice(); request_device_callback_map_[request_device_serial] = base::BindOnce( - [](dawn_wire::WireClient* wire_client, + [](scoped_refptr<DawnWireServices> dawn_wire, dawn_wire::ReservedDevice reservation, base::OnceCallback<void(WGPUDevice)> callback, bool success) { WGPUDevice device = reservation.device; if (!success) { - wire_client->ReclaimDeviceReservation(reservation); + dawn_wire->wire_client()->ReclaimDeviceReservation(reservation); device = nullptr; } std::move(callback).Run(device); }, - wire_client_.get(), reservation, std::move(request_device_callback)); + dawn_wire_, reservation, std::move(request_device_callback)); dawn_wire::SerializeWGPUDeviceProperties( &requested_device_properties, reinterpret_cast<char*>(buffer.address())); @@ -586,7 +647,7 @@ // and need to be resolved prior to the AssociateMailbox command. Otherwise // the service side might not know, for example that the previous texture // using that ID has been released. - wire_serializer_->Commit(); + dawn_wire_->serializer()->Commit(); helper_->AssociateMailboxImmediate(device_id, device_generation, texture_id, texture_generation, usage, mailbox); #endif @@ -597,7 +658,7 @@ #if BUILDFLAG(USE_DAWN) // Commit previous Dawn commands that might be rendering to the texture, prior // to Dissociating the shared image from that texture. - wire_serializer_->Commit(); + dawn_wire_->serializer()->Commit(); helper_->DissociateMailbox(texture_id, texture_generation); #endif }
diff --git a/gpu/command_buffer/client/webgpu_implementation.h b/gpu/command_buffer/client/webgpu_implementation.h index f79c3d7..0720033 100644 --- a/gpu/command_buffer/client/webgpu_implementation.h +++ b/gpu/command_buffer/client/webgpu_implementation.h
@@ -28,8 +28,7 @@ namespace gpu { namespace webgpu { -class DawnClientMemoryTransferService; -class DawnClientSerializer; +class DawnWireServices; class WEBGPU_EXPORT WebGPUImplementation final : public WebGPUInterface, public ImplementationBase { @@ -115,11 +114,10 @@ void OnGpuControlReturnData(base::span<const uint8_t> data) final; // WebGPUInterface implementation - const DawnProcTable& GetProcs() const override; void FlushCommands() override; void EnsureAwaitingFlush(bool* needs_flush) override; void FlushAwaitingCommands() override; - void DisconnectContextAndDestroyServer() override; + scoped_refptr<APIChannel> GetAPIChannel() const override; ReservedTexture ReserveTexture(WGPUDevice device) override; void RequestAdapterAsync( PowerPreference power_preference, @@ -138,12 +136,11 @@ void CheckGLError() {} DawnRequestAdapterSerial NextRequestAdapterSerial(); DawnRequestDeviceSerial NextRequestDeviceSerial(); + void LoseContext(); WebGPUCmdHelper* helper_; #if BUILDFLAG(USE_DAWN) - std::unique_ptr<DawnClientMemoryTransferService> memory_transfer_service_; - std::unique_ptr<DawnClientSerializer> wire_serializer_; - std::unique_ptr<dawn_wire::WireClient> wire_client_; + scoped_refptr<DawnWireServices> dawn_wire_; #endif WGPUDevice deprecated_default_device_ = nullptr;
diff --git a/gpu/command_buffer/client/webgpu_interface.h b/gpu/command_buffer/client/webgpu_interface.h index df6094f3..8aee665 100644 --- a/gpu/command_buffer/client/webgpu_interface.h +++ b/gpu/command_buffer/client/webgpu_interface.h
@@ -24,12 +24,28 @@ uint32_t deviceGeneration; }; +// APIChannel is a RefCounted class which holds the Dawn wire client. +class APIChannel : public base::RefCounted<APIChannel> { + public: + // Get the proc table. + // As long as a reference to this APIChannel alive, it is valid to + // call these procs. + virtual const DawnProcTable& GetProcs() const = 0; + + // Disconnect. All commands using the WebGPU API should become a + // no-op and server-side resources can be freed. + virtual void Disconnect() = 0; + + protected: + friend class base::RefCounted<APIChannel>; + APIChannel() = default; + virtual ~APIChannel() = default; +}; + class WebGPUInterface : public InterfaceBase { public: - WebGPUInterface() {} - virtual ~WebGPUInterface() {} - - virtual const DawnProcTable& GetProcs() const = 0; + WebGPUInterface() = default; + virtual ~WebGPUInterface() = default; // Flush all commands. virtual void FlushCommands() = 0; @@ -44,9 +60,8 @@ // nothing. virtual void FlushAwaitingCommands() = 0; - // Disconnect. All commands should become a no-op and server-side resources - // can be freed. - virtual void DisconnectContextAndDestroyServer() = 0; + // Get a strong reference to the APIChannel backing the implementation. + virtual scoped_refptr<APIChannel> GetAPIChannel() const = 0; virtual ReservedTexture ReserveTexture(WGPUDevice device) = 0; virtual void RequestAdapterAsync(
diff --git a/gpu/command_buffer/client/webgpu_interface_stub.cc b/gpu/command_buffer/client/webgpu_interface_stub.cc index b724897..9aca0fe0 100644 --- a/gpu/command_buffer/client/webgpu_interface_stub.cc +++ b/gpu/command_buffer/client/webgpu_interface_stub.cc
@@ -7,10 +7,34 @@ namespace gpu { namespace webgpu { -WebGPUInterfaceStub::WebGPUInterfaceStub() = default; +namespace { + +class APIChannelStub : public APIChannel { + public: + APIChannelStub() = default; + + const DawnProcTable& GetProcs() const override { return procs_; } + void Disconnect() override {} + + DawnProcTable* procs() { return &procs_; } + + private: + ~APIChannelStub() override = default; + + DawnProcTable procs_ = {}; +}; + +} // anonymous namespace + +WebGPUInterfaceStub::WebGPUInterfaceStub() + : api_channel_(base::MakeRefCounted<APIChannelStub>()) {} WebGPUInterfaceStub::~WebGPUInterfaceStub() = default; +DawnProcTable* WebGPUInterfaceStub::procs() { + return static_cast<APIChannelStub*>(api_channel_.get())->procs(); +} + // InterfaceBase implementation. void WebGPUInterfaceStub::GenSyncTokenCHROMIUM(GLbyte* sync_token) {} void WebGPUInterfaceStub::GenUnverifiedSyncTokenCHROMIUM(GLbyte* sync_token) {} @@ -20,13 +44,12 @@ void WebGPUInterfaceStub::ShallowFlushCHROMIUM() {} // WebGPUInterface implementation -const DawnProcTable& WebGPUInterfaceStub::GetProcs() const { - return null_procs_; +scoped_refptr<APIChannel> WebGPUInterfaceStub::GetAPIChannel() const { + return api_channel_; } void WebGPUInterfaceStub::FlushCommands() {} void WebGPUInterfaceStub::EnsureAwaitingFlush(bool* needs_flush) {} void WebGPUInterfaceStub::FlushAwaitingCommands() {} -void WebGPUInterfaceStub::DisconnectContextAndDestroyServer() {} ReservedTexture WebGPUInterfaceStub::ReserveTexture(WGPUDevice) { return {nullptr, 0, 0, 0, 0}; }
diff --git a/gpu/command_buffer/client/webgpu_interface_stub.h b/gpu/command_buffer/client/webgpu_interface_stub.h index 2c7d9d5f..e8b5da5a 100644 --- a/gpu/command_buffer/client/webgpu_interface_stub.h +++ b/gpu/command_buffer/client/webgpu_interface_stub.h
@@ -24,11 +24,10 @@ void ShallowFlushCHROMIUM() override; // WebGPUInterface implementation - const DawnProcTable& GetProcs() const override; + scoped_refptr<APIChannel> GetAPIChannel() const override; void FlushCommands() override; void EnsureAwaitingFlush(bool* needs_flush) override; void FlushAwaitingCommands() override; - void DisconnectContextAndDestroyServer() override; ReservedTexture ReserveTexture(WGPUDevice device) override; void RequestAdapterAsync( PowerPreference power_preference, @@ -47,8 +46,11 @@ // this file instead of having to edit some template or the code generator. #include "gpu/command_buffer/client/webgpu_interface_stub_autogen.h" + protected: + DawnProcTable* procs(); + private: - DawnProcTable null_procs_; + scoped_refptr<APIChannel> api_channel_; }; } // namespace webgpu
diff --git a/gpu/command_buffer/common/webgpu_cmd_format_autogen.h b/gpu/command_buffer/common/webgpu_cmd_format_autogen.h index 6f91209..a82850b 100644 --- a/gpu/command_buffer/common/webgpu_cmd_format_autogen.h +++ b/gpu/command_buffer/common/webgpu_cmd_format_autogen.h
@@ -282,30 +282,4 @@ offsetof(RequestDevice, request_device_properties_size) == 28, "offset of RequestDevice request_device_properties_size should be 28"); -struct DestroyServer { - typedef DestroyServer ValueType; - static const CommandId kCmdId = kDestroyServer; - static const cmd::ArgFlags kArgFlags = cmd::kFixed; - static const uint8_t cmd_flags = CMD_FLAG_SET_TRACE_LEVEL(3); - - static uint32_t ComputeSize() { - return static_cast<uint32_t>(sizeof(ValueType)); // NOLINT - } - - void SetHeader() { header.SetCmd<ValueType>(); } - - void Init() { SetHeader(); } - - void* Set(void* cmd) { - static_cast<ValueType*>(cmd)->Init(); - return NextCmdAddress<ValueType>(cmd); - } - - gpu::CommandHeader header; -}; - -static_assert(sizeof(DestroyServer) == 4, "size of DestroyServer should be 4"); -static_assert(offsetof(DestroyServer, header) == 0, - "offset of DestroyServer header should be 0"); - #endif // GPU_COMMAND_BUFFER_COMMON_WEBGPU_CMD_FORMAT_AUTOGEN_H_
diff --git a/gpu/command_buffer/common/webgpu_cmd_format_test_autogen.h b/gpu/command_buffer/common/webgpu_cmd_format_test_autogen.h index ae13151..3234f419 100644 --- a/gpu/command_buffer/common/webgpu_cmd_format_test_autogen.h +++ b/gpu/command_buffer/common/webgpu_cmd_format_test_autogen.h
@@ -112,13 +112,4 @@ CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd)); } -TEST_F(WebGPUFormatTest, DestroyServer) { - cmds::DestroyServer& cmd = *GetBufferAs<cmds::DestroyServer>(); - void* next_cmd = cmd.Set(&cmd); - EXPECT_EQ(static_cast<uint32_t>(cmds::DestroyServer::kCmdId), - cmd.header.command); - EXPECT_EQ(sizeof(cmd), cmd.header.size * 4u); - CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd)); -} - #endif // GPU_COMMAND_BUFFER_COMMON_WEBGPU_CMD_FORMAT_TEST_AUTOGEN_H_
diff --git a/gpu/command_buffer/common/webgpu_cmd_ids_autogen.h b/gpu/command_buffer/common/webgpu_cmd_ids_autogen.h index 4241d0f..3e70690 100644 --- a/gpu/command_buffer/common/webgpu_cmd_ids_autogen.h +++ b/gpu/command_buffer/common/webgpu_cmd_ids_autogen.h
@@ -16,8 +16,7 @@ OP(AssociateMailboxImmediate) /* 257 */ \ OP(DissociateMailbox) /* 258 */ \ OP(RequestAdapter) /* 259 */ \ - OP(RequestDevice) /* 260 */ \ - OP(DestroyServer) /* 261 */ + OP(RequestDevice) /* 260 */ enum CommandId { kOneBeforeStartPoint =
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc index 99fe542..448bfc67 100644 --- a/gpu/command_buffer/service/webgpu_decoder_impl.cc +++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -917,14 +917,6 @@ sizeof(return_request_device_info))); } -error::Error WebGPUDecoderImpl::HandleDestroyServer( - uint32_t immediate_data_size, - const volatile void* cmd_data) { - Destroy(false); - - return error::kNoError; -} - error::Error WebGPUDecoderImpl::HandleRequestAdapter( uint32_t immediate_data_size, const volatile void* cmd_data) {
diff --git a/gpu/command_buffer/tests/webgpu_test.cc b/gpu/command_buffer/tests/webgpu_test.cc index dd59e917..eb6b2fac 100644 --- a/gpu/command_buffer/tests/webgpu_test.cc +++ b/gpu/command_buffer/tests/webgpu_test.cc
@@ -123,7 +123,7 @@ RunPendingTasks(); } - DawnProcTable procs = webgpu()->GetProcs(); + DawnProcTable procs = webgpu()->GetAPIChannel()->GetProcs(); dawnProcSetProcs(&procs); } @@ -291,52 +291,6 @@ EXPECT_TRUE(called); } -TEST_F(WebGPUTest, RequestAdapterAfterServerDestroyed) { - if (!WebGPUSupported()) { - LOG(ERROR) << "Test skipped because WebGPU isn't supported"; - return; - } - - Initialize(WebGPUTest::Options()); - - webgpu()->DisconnectContextAndDestroyServer(); - - bool called = false; - webgpu()->RequestAdapterAsync( - webgpu::PowerPreference::kDefault, - base::BindOnce( - [](bool* called, int32_t adapter_id, const WGPUDeviceProperties&, - const char*) { - EXPECT_EQ(adapter_id, -1); - *called = true; - }, - &called)); - RunPendingTasks(); - EXPECT_TRUE(called); -} - -TEST_F(WebGPUTest, RequestDeviceAfterServerDestroyed) { - if (!WebGPUSupported()) { - LOG(ERROR) << "Test skipped because WebGPU isn't supported"; - return; - } - - Initialize(WebGPUTest::Options()); - - webgpu()->DisconnectContextAndDestroyServer(); - - bool called = false; - webgpu()->RequestDeviceAsync(GetAdapterId(), GetDeviceProperties(), - base::BindOnce( - [](bool* called, WGPUDevice device) { - EXPECT_EQ(device, nullptr); - *called = true; - }, - &called)); - RunPendingTasks(); - EXPECT_TRUE(called); -} - TEST_F(WebGPUTest, RequestDeviceWitUnsupportedExtension) { if (!WebGPUSupported()) { LOG(ERROR) << "Test skipped because WebGPU isn't supported";
diff --git a/gpu/command_buffer/webgpu_cmd_buffer_functions.txt b/gpu/command_buffer/webgpu_cmd_buffer_functions.txt index 654ec8ff..8ac5f0da 100644 --- a/gpu/command_buffer/webgpu_cmd_buffer_functions.txt +++ b/gpu/command_buffer/webgpu_cmd_buffer_functions.txt
@@ -11,4 +11,3 @@ GL_APICALL void GL_APIENTRY wgDissociateMailbox (GLuint texture_id, GLuint texture_generation); GL_APICALL void GL_APIENTRY wgRequestAdapter (GLuint64 request_adapter_serial, EnumClassPowerPreference power_preference = PowerPreference::kDefault); GL_APICALL void GL_APIENTRY wgRequestDevice (GLuint64 request_device_serial, GLuint adapter_service_id, GLuint device_id, GLuint device_generation, const char* dawn_request_device_properties, size_t request_device_properties_size); -GL_APICALL void GL_APIENTRY wgDestroyServer (void);
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json index 7833e21b..5b055b08 100644 --- a/gpu/config/gpu_driver_bug_list.json +++ b/gpu/config/gpu_driver_bug_list.json
@@ -3760,6 +3760,22 @@ "features": [ "disable_canvas_oop_rasterization" ] + }, + { + "id": 379, + "cr_bugs": [1217298], + "description": "Disable VP9 HW encode on Intel Gen 9.5 SoCs using the i965 VA driver backend", + "os": { + "type": "chromeos" + }, + "vendor_id": "0x8086", + "intel_gpu_series": [ + "kabylake", + "geminilake" + ], + "features": [ + "disable_accelerated_vp9_encode" + ] } ] }
diff --git a/gpu/config/gpu_workaround_list.txt b/gpu/config/gpu_workaround_list.txt index ff63e95..e3ffc937 100644 --- a/gpu/config/gpu_workaround_list.txt +++ b/gpu/config/gpu_workaround_list.txt
@@ -18,6 +18,7 @@ disable_accelerated_vp8_decode disable_accelerated_vp8_encode disable_accelerated_vp9_decode +disable_accelerated_vp9_encode disable_accelerated_vp9_profile2_decode disable_async_readpixels disable_av_sample_buffer_display_layer
diff --git a/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.h b/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.h index 9ff08cb6..c57ab57 100644 --- a/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.h +++ b/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.h
@@ -22,6 +22,9 @@ // visible. - (void)recordDeviceOrientationChanged:(UIDeviceOrientation)orientation; +// Record metrics for when the user has tapped on the feed preview. +- (void)recordDiscoverFeedPreviewTapped; + // Record metrics for when the user selects the 'Learn More' item in the feed // header menu. - (void)recordHeaderMenuLearnMoreTapped;
diff --git a/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm b/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm index faf418d..6695184 100644 --- a/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm +++ b/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm
@@ -107,6 +107,8 @@ "ContentSuggestions.Feed.CardAction.ReportContent"; const char kDiscoverFeedUserActionReportContentClosed[] = "ContentSuggestions.Feed.CardAction.ClosedReportContent"; +const char kDiscoverFeedUserActionPreviewTapped[] = + "ContentSuggestions.Feed.CardAction.TapPreview"; // User action names for feed header menu. const char kDiscoverFeedUserActionManageActivityTapped[] = @@ -225,6 +227,13 @@ } } +- (void)recordDiscoverFeedPreviewTapped { + [self recordDiscoverFeedUserActionHistogram:FeedUserActionType:: + kTappedDiscoverFeedPreview]; + base::RecordAction( + base::UserMetricsAction(kDiscoverFeedUserActionPreviewTapped)); +} + - (void)recordHeaderMenuLearnMoreTapped { [self recordDiscoverFeedUserActionHistogram:FeedUserActionType:: kTappedLearnMore];
diff --git a/ipc/ipc_sync_message_unittest.h b/ipc/ipc_sync_message_unittest.h index da1699a7..924285b8 100644 --- a/ipc/ipc_sync_message_unittest.h +++ b/ipc/ipc_sync_message_unittest.h
@@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// no-include-guard-because-multiply-included +// Multiply-included message file, hence no include guard here. + #include <string> #include "ipc/ipc_message_macros.h"
diff --git a/media/base/video_decoder.cc b/media/base/video_decoder.cc index a593bb3..2133c50e06 100644 --- a/media/base/video_decoder.cc +++ b/media/base/video_decoder.cc
@@ -4,7 +4,10 @@ #include "media/base/video_decoder.h" +#include <algorithm> + #include "base/command_line.h" +#include "base/cxx17_backports.h" #include "base/strings/string_number_conversions.h" #include "base/system/sys_info.h" #include "media/base/limits.h" @@ -56,9 +59,9 @@ // zero threads; I.e., decoding will execute on the calling thread. Therefore, // at least two threads are required to allow decoding to progress outside of // each Decode() call. - return std::min(std::max(desired_threads, - static_cast<int>(limits::kMinVideoDecodeThreads)), - static_cast<int>(limits::kMaxVideoDecodeThreads)); + return base::clamp(desired_threads, + static_cast<int>(limits::kMinVideoDecodeThreads), + static_cast<int>(limits::kMaxVideoDecodeThreads)); } } // namespace media
diff --git a/media/capture/video/chromeos/camera_app_device_impl.cc b/media/capture/video/chromeos/camera_app_device_impl.cc index 5d9f8c5..df0d299 100644 --- a/media/capture/video/chromeos/camera_app_device_impl.cc +++ b/media/capture/video/chromeos/camera_app_device_impl.cc
@@ -4,6 +4,8 @@ #include "media/capture/video/chromeos/camera_app_device_impl.h" +#include <cmath> + #include "base/bind_post_task.h" #include "gpu/ipc/common/gpu_memory_buffer_impl.h" #include "media/base/bind_to_current_loop.h" @@ -201,7 +203,8 @@ } void CameraAppDeviceImpl::DetectDocumentCorners( - std::unique_ptr<gpu::GpuMemoryBufferImpl> gmb) { + std::unique_ptr<gpu::GpuMemoryBufferImpl> gmb, + VideoRotation rotation) { DCHECK(gmb); if (!gmb->Map()) { LOG(ERROR) << "Failed to map frame buffer"; @@ -238,7 +241,7 @@ base::BindPostTask( mojo_task_runner_, base::BindOnce(&CameraAppDeviceImpl::OnDetectedDocumentCorners, - weak_ptr_factory_for_mojo_.GetWeakPtr()))); + weak_ptr_factory_for_mojo_.GetWeakPtr(), rotation))); } } @@ -455,6 +458,7 @@ } void CameraAppDeviceImpl::OnDetectedDocumentCorners( + VideoRotation rotation, bool success, const std::vector<gfx::PointF>& corners) { base::AutoLock lock(document_corners_observer_lock_); @@ -464,8 +468,33 @@ return; } + // Rotate a point in coordination space {x: [0.0, 1.0], y: [0.0, 1.0]} with + // anchor point {x: 0.5, y: 0.5}. + auto rotate_corner = [&](const gfx::PointF& corner) -> gfx::PointF { + float x = base::clamp(corner.x(), 0.0f, 1.0f); + float y = base::clamp(corner.y(), 0.0f, 1.0f); + + switch (rotation) { + case VIDEO_ROTATION_0: + return {x, y}; + case VIDEO_ROTATION_90: + return {y, 1.0f - x}; + case VIDEO_ROTATION_180: + return {1.0f - x, 1.0f - y}; + case VIDEO_ROTATION_270: + return {1.0f - y, x}; + default: + NOTREACHED(); + } + }; + + std::vector<gfx::PointF> rotated_corners; + for (auto& corner : corners) { + rotated_corners.push_back(rotate_corner(corner)); + } + for (auto& observer : document_corners_observers_) { - observer.second->OnDocumentCornersUpdated(corners); + observer.second->OnDocumentCornersUpdated(rotated_corners); } }
diff --git a/media/capture/video/chromeos/camera_app_device_impl.h b/media/capture/video/chromeos/camera_app_device_impl.h index 9d800e3..ee3719a 100644 --- a/media/capture/video/chromeos/camera_app_device_impl.h +++ b/media/capture/video/chromeos/camera_app_device_impl.h
@@ -17,6 +17,7 @@ #include "base/single_thread_task_runner.h" #include "base/synchronization/lock.h" #include "chromeos/components/camera_app_ui/document_scanner_service_client.h" +#include "media/base/video_transformation.h" #include "media/capture/capture_export.h" #include "media/capture/mojom/image_capture.mojom.h" #include "media/capture/video/chromeos/mojom/camera3.mojom.h" @@ -123,7 +124,8 @@ bool ShouldDetectDocumentCorners(); // Detect document corners on the frame given by its gpu memory buffer. - void DetectDocumentCorners(std::unique_ptr<gpu::GpuMemoryBufferImpl> gmb); + void DetectDocumentCorners(std::unique_ptr<gpu::GpuMemoryBufferImpl> gmb, + VideoRotation rotation); // cros::mojom::CameraAppDevice implementations. void GetCameraInfo(GetCameraInfoCallback callback) override; @@ -163,7 +165,8 @@ private: static void DisableEeNr(ReprocessTask* task); - void OnDetectedDocumentCorners(bool success, + void OnDetectedDocumentCorners(VideoRotation rotation, + bool success, const std::vector<gfx::PointF>& corners); void OnMojoConnectionError();
diff --git a/media/capture/video/chromeos/request_manager.cc b/media/capture/video/chromeos/request_manager.cc index 1de8c8d..980a498 100644 --- a/media/capture/video/chromeos/request_manager.cc +++ b/media/capture/video/chromeos/request_manager.cc
@@ -970,17 +970,6 @@ stream_type, buffer_ipc_id, &format); CHECK(buffer); - auto camera_app_device = - CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice( - device_id_); - if (camera_app_device && stream_type == StreamType::kPreviewOutput && - camera_app_device->ShouldDetectDocumentCorners()) { - camera_app_device->DetectDocumentCorners( - stream_buffer_manager_->CreateGpuMemoryBuffer( - buffer->handle_provider->GetGpuMemoryBufferHandle(), format, - gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE)); - } - // TODO: Figure out the right color space for the camera frame. We may need // to populate the camera metadata with the color space reported by the V4L2 // device. @@ -1008,6 +997,19 @@ // All frames are pre-rotated to the display orientation. metadata.transformation = VIDEO_ROTATION_0; } + + auto camera_app_device = + CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice( + device_id_); + if (camera_app_device && stream_type == StreamType::kPreviewOutput && + camera_app_device->ShouldDetectDocumentCorners()) { + camera_app_device->DetectDocumentCorners( + stream_buffer_manager_->CreateGpuMemoryBuffer( + buffer->handle_provider->GetGpuMemoryBufferHandle(), format, + gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE), + metadata.transformation->rotation); + } + device_context_->SubmitCapturedVideoCaptureBuffer( client_type, std::move(*buffer), format, pending_result.reference_time, pending_result.timestamp, metadata);
diff --git a/media/gpu/gpu_video_encode_accelerator_factory.cc b/media/gpu/gpu_video_encode_accelerator_factory.cc index 4be2b053..21b82df 100644 --- a/media/gpu/gpu_video_encode_accelerator_factory.cc +++ b/media/gpu/gpu_video_encode_accelerator_factory.cc
@@ -191,6 +191,13 @@ }); } + if (gpu_workarounds.disable_accelerated_vp9_encode) { + base::EraseIf(profiles, [](const auto& vea_profile) { + return vea_profile.profile >= VP9PROFILE_PROFILE0 && + vea_profile.profile <= VP9PROFILE_PROFILE3; + }); + } + if (gpu_workarounds.disable_accelerated_h264_encode) { base::EraseIf(profiles, [](const auto& vea_profile) { return vea_profile.profile >= H264PROFILE_MIN &&
diff --git a/media/gpu/vaapi/vaapi_video_decoder.cc b/media/gpu/vaapi/vaapi_video_decoder.cc index 6245147d..47fa4cb2 100644 --- a/media/gpu/vaapi/vaapi_video_decoder.cc +++ b/media/gpu/vaapi/vaapi_video_decoder.cc
@@ -35,6 +35,7 @@ #include "media/gpu/vaapi/vp8_vaapi_video_decoder_delegate.h" #include "media/gpu/vaapi/vp9_vaapi_video_decoder_delegate.h" #include "media/media_buildflags.h" +#include "ui/gfx/buffer_format_util.h" #if BUILDFLAG(ENABLE_PLATFORM_HEVC_DECODING) #include "media/gpu/vaapi/h265_vaapi_video_decoder_delegate.h" @@ -223,21 +224,20 @@ if (config.is_encrypted()) { #if !BUILDFLAG(IS_CHROMEOS_ASH) - SetState(State::kError); + SetErrorState("encrypted content is not supported"); std::move(init_cb).Run(StatusCode::kEncryptedContentUnsupported); return; #else if (!cdm_context || !cdm_context->GetChromeOsCdmContext()) { - LOG(ERROR) << "Cannot support encrypted stream w/out ChromeOsCdmContext"; - SetState(State::kError); + SetErrorState("cannot support encrypted stream w/out ChromeOsCdmContext"); std::move(init_cb).Run(StatusCode::kDecoderMissingCdmForEncryptedContent); return; } if (config.codec() != kCodecH264 && config.codec() != kCodecVP9 && config.codec() != kCodecHEVC) { - VLOGF(1) - << "Vaapi decoder does not support this codec for encrypted content"; - SetState(State::kError); + SetErrorState( + base::StringPrintf("%s is not supported for encrypted content", + GetCodecName(config.codec()).c_str())); std::move(init_cb).Run(StatusCode::kEncryptedContentUnsupported); return; } @@ -254,8 +254,7 @@ } else if (config.codec() == kCodecHEVC && !base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableClearHevcForTesting)) { - DVLOG(1) << "Clear HEVC content is not supported"; - SetState(State::kError); + SetErrorState("clear HEVC content is not supported"); std::move(init_cb).Run(StatusCode::kClearContentUnsupported); return; #endif @@ -278,9 +277,9 @@ UMA_HISTOGRAM_BOOLEAN("Media.VaapiVideoDecoder.VaapiWrapperCreationSuccess", vaapi_wrapper_.get()); if (!vaapi_wrapper_.get()) { - VLOGF(1) << "Failed initializing VAAPI for profile " - << GetProfileName(profile); - SetState(State::kError); + SetErrorState( + base::StringPrintf("failed initializing VaapiWrapper for profile %s, ", + GetProfileName(profile).c_str())); std::move(init_cb).Run(StatusCode::kDecoderUnsupportedProfile); return; } @@ -292,7 +291,7 @@ : config.encryption_scheme(); auto accel_status = CreateAcceleratedVideoDecoder(); if (!accel_status.is_ok()) { - SetState(State::kError); + SetErrorState("failed to create decoder delegate"); std::move(init_cb).Run(std::move(accel_status)); return; } @@ -421,13 +420,11 @@ SetState(State::kWaitingForOutput); break; case AcceleratedVideoDecoder::kNeedContextUpdate: - LOG(ERROR) << "Context updates not supported"; - SetState(State::kError); + SetErrorState("context updates not supported"); break; case AcceleratedVideoDecoder::kDecodeError: - LOG(ERROR) << "Error decoding stream"; UMA_HISTOGRAM_BOOLEAN("Media.VaapiVideoDecoder.DecodeError", true); - SetState(State::kError); + SetErrorState("error decoding stream"); break; case AcceleratedVideoDecoder::kTryAgain: DVLOG(1) << "Decoder going into the waiting for protected state"; @@ -486,16 +483,14 @@ scoped_refptr<gfx::NativePixmap> pixmap = CreateNativePixmapDmaBuf(frame.get()); if (!pixmap) { - LOG(ERROR) << "Failed to create NativePixmap from VideoFrame"; - SetState(State::kError); + SetErrorState("failed to create NativePixmap from VideoFrame"); return nullptr; } va_surface = vaapi_wrapper_->CreateVASurfaceForPixmap(std::move(pixmap), transcryption_); if (!va_surface || va_surface->id() == VA_INVALID_ID) { - LOG(ERROR) << "Failed to create VASurface from VideoFrame"; - SetState(State::kError); + SetErrorState("failed to create VASurface from VideoFrame"); return nullptr; } @@ -674,7 +669,7 @@ const absl::optional<VideoPixelFormat> format = GetPixelFormatForBitDepth(bit_depth); if (!format) { - SetState(State::kError); + SetErrorState(base::StringPrintf("unsupported bit depth: %d", bit_depth)); return; } @@ -696,8 +691,7 @@ gfx::Rect output_visible_rect = decoder_->GetVisibleRect(); gfx::Size output_pic_size = decoder_->GetPicSize(); if (output_pic_size.IsEmpty()) { - DLOG(ERROR) << "Empty picture size in decoder"; - SetState(State::kError); + SetErrorState("|decoder_| returned an empty picture size"); return; } const auto format_fourcc = Fourcc::FromVideoPixelFormat(*format); @@ -764,7 +758,9 @@ VideoPixelFormatToGfxBufferFormat(*format); if (!buffer_format) { decode_to_output_scale_factor_.reset(); - SetState(State::kError); + SetErrorState( + base::StringPrintf("unsupported pixel format: %s", + VideoPixelFormatToString(*format).c_str())); return; } const uint32_t va_fourcc = @@ -773,7 +769,9 @@ VaapiWrapper::BufferFormatToVARTFormat(*buffer_format); if (!va_fourcc || !va_rt_format) { decode_to_output_scale_factor_.reset(); - SetState(State::kError); + SetErrorState( + base::StringPrintf("VA-API does not support: %s", + gfx::BufferFormatToString(*buffer_format))); return; } const gfx::Size decoder_pic_size = decoder_->GetPicSize(); @@ -783,7 +781,7 @@ /*visible_size=*/absl::nullopt, va_fourcc); if (scoped_va_surfaces.empty()) { decode_to_output_scale_factor_.reset(); - SetState(State::kError); + SetErrorState("failed creating VASurfaces"); return; } @@ -796,8 +794,7 @@ if (!frame_pool_->Initialize( *format_fourcc, output_pic_size, output_visible_rect, natural_size, decoder_->GetRequiredNumOfPictures(), !!cdm_context_ref_)) { - DLOG(WARNING) << "Failed Initialize()ing the frame pool."; - SetState(State::kError); + SetErrorState("failed Initialize()ing the frame pool"); return; } @@ -815,8 +812,7 @@ base::BindRepeating(&ReportVaapiErrorToUMA, "Media.VaapiVideoDecoder.VAAPIError")); if (!new_vaapi_wrapper.get()) { - DLOG(WARNING) << "Failed creating VaapiWrapper"; - SetState(State::kError); + SetErrorState("failed (re)creating VaapiWrapper"); return; } decoder_delegate_->set_vaapi_wrapper(new_vaapi_wrapper.get()); @@ -824,8 +820,7 @@ } if (!vaapi_wrapper_->CreateContext(decoder_->GetPicSize())) { - VLOGF(1) << "Failed creating context"; - SetState(State::kError); + SetErrorState("failed creating VAContext"); return; } @@ -898,8 +893,7 @@ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!success) { - LOG(ERROR) << "Terminating decoding after failed protected update"; - SetState(State::kError); + SetErrorState("terminating decoding after failed protected update"); return; } @@ -925,8 +919,7 @@ // Flush will block until SurfaceReady() has been called for every frame // currently decoding. if (!decoder_->Flush()) { - LOG(ERROR) << "Failed to flush the decoder"; - SetState(State::kError); + SetErrorState("failed to flush the decoder delegate"); return; } @@ -961,7 +954,7 @@ // internal data structures. decoder_delegate_->OnVAContextDestructionSoon(); if (!CreateAcceleratedVideoDecoder().is_ok()) { - SetState(State::kError); + SetErrorState("failed to (re)create decoder/delegate"); std::move(reset_cb).Run(); return; } @@ -1091,13 +1084,18 @@ case State::kError: ClearDecodeTaskQueue(DecodeStatus::DECODE_ERROR); break; - default: - NOTREACHED() << "Invalid state change"; } state_ = state; } +void VaapiVideoDecoder::SetErrorState(std::string message) { + LOG(ERROR) << message; + if (media_log_) + MEDIA_LOG(ERROR, media_log_) << "VaapiVideoDecoder: " << message; + SetState(State::kError); +} + void VaapiVideoDecoder::ReturnDecodeSurfaceToPool( std::unique_ptr<ScopedVASurface> surface, VASurfaceID) {
diff --git a/media/gpu/vaapi/vaapi_video_decoder.h b/media/gpu/vaapi/vaapi_video_decoder.h index 5407010..15d52ec 100644 --- a/media/gpu/vaapi/vaapi_video_decoder.h +++ b/media/gpu/vaapi/vaapi_video_decoder.h
@@ -153,6 +153,10 @@ // Change the current |state_| to the specified |state|. void SetState(State state); + // Tell SetState() to change the |state_| to kError and send |message| to + // MediaLog and to LOG(ERROR). + void SetErrorState(std::string message); + // Callback for the CDM to notify |this|. void OnCdmContextEvent(CdmContext::Event event);
diff --git a/media/gpu/video_encode_accelerator_perf_tests.cc b/media/gpu/video_encode_accelerator_perf_tests.cc index 839c276..3e2dc4d 100644 --- a/media/gpu/video_encode_accelerator_perf_tests.cc +++ b/media/gpu/video_encode_accelerator_perf_tests.cc
@@ -398,31 +398,69 @@ } // Video encode test class. Performs setup and teardown for each single test. +// It measures the performance in encoding NV12 GpuMemoryBuffer based +// VideoFrame. class VideoEncoderTest : public ::testing::Test { public: - // Create a new video encoder instance. + // Creates VideoEncoder for encoding NV12 GpuMemoryBuffer based VideoFrames. + // The input VideoFrames are provided every 1 / |encoder_rate| seconds if it + // is specified. Or they are provided as soon as the previous input VideoFrame + // is consumed by VideoEncoder. |measure_quality| measures SSIM and PSNR + // values of encoded bitstream comparing the original input VideoFrames. std::unique_ptr<VideoEncoder> CreateVideoEncoder( - Video* video, - VideoCodecProfile profile, - const media::VideoBitrateAllocation& bitrate, - uint32_t encoder_rate = 0) { - auto performance_evaluator = std::make_unique<PerformanceEvaluator>(); - performance_evaluator_ = performance_evaluator.get(); + absl::optional<uint32_t> encode_rate, + bool measure_quality) { + Video* video = g_env->GenerateNV12Video(); + VideoCodecProfile profile = g_env->Profile(); + const media::VideoBitrateAllocation& bitrate = g_env->Bitrate(); + std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors; - bitstream_processors.push_back(std::move(performance_evaluator)); - return CreateVideoEncoderWithProcessors( - video, profile, bitrate, encoder_rate, std::move(bitstream_processors)); + if (measure_quality) { + bitstream_processors = + CreateBitstreamProcessorsForQualityPerformance(video, profile); + } else { + auto performance_evaluator = std::make_unique<PerformanceEvaluator>(); + performance_evaluator_ = performance_evaluator.get(); + bitstream_processors.push_back(std::move(performance_evaluator)); + } + LOG_ASSERT(!bitstream_processors.empty()) + << "Failed to create bitstream processors"; + + VideoEncoderClientConfig config(video, profile, g_env->SpatialLayers(), + bitrate); + config.input_storage_type = + VideoEncodeAccelerator::Config::StorageType::kGpuMemoryBuffer; + config.num_frames_to_encode = kNumFramesToEncodeForPerformance; + if (encode_rate) { + config.encode_interval = + base::TimeDelta::FromSeconds(1u) / encode_rate.value(); + } + + auto video_encoder = + VideoEncoder::Create(config, g_env->GetGpuMemoryBufferFactory(), + std::move(bitstream_processors)); + LOG_ASSERT(video_encoder); + LOG_ASSERT(video_encoder->Initialize(video)); + + return video_encoder; } - // Create a new video encoder instance for quality performance tests. - std::unique_ptr<VideoEncoder> CreateVideoEncoderForQualityPerformance( - Video* video, - VideoCodecProfile profile, - const media::VideoBitrateAllocation& bitrate) { + protected: + PerformanceEvaluator* performance_evaluator_; + SSIMVideoFrameValidator* ssim_validator_; + PSNRVideoFrameValidator* psnr_validator_; + + private: + // Create bitstream processors for quality performance tests. + std::vector<std::unique_ptr<BitstreamProcessor>> + CreateBitstreamProcessorsForQualityPerformance(Video* video, + VideoCodecProfile profile) { + std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors; + raw_data_helper_ = RawDataHelper::Create(video); if (!raw_data_helper_) { LOG(ERROR) << "Failed to create raw data helper"; - return nullptr; + return bitstream_processors; } std::vector<std::unique_ptr<VideoFrameProcessor>> video_frame_processors; @@ -455,34 +493,9 @@ decoder_config, kNumFramesToEncodeForPerformance - 1, std::move(video_frame_processors)); LOG_ASSERT(bitstream_validator); - std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors; bitstream_processors.push_back(std::move(bitstream_validator)); - return CreateVideoEncoderWithProcessors(video, profile, bitrate, - /*encoder_rate=*/0, - std::move(bitstream_processors)); - } - std::unique_ptr<VideoEncoder> CreateVideoEncoderWithProcessors( - Video* video, - VideoCodecProfile profile, - const media::VideoBitrateAllocation& bitrate, - uint32_t encoder_rate, - std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors) { - LOG_ASSERT(video); - VideoEncoderClientConfig config(video, profile, g_env->SpatialLayers(), - bitrate); - config.num_frames_to_encode = kNumFramesToEncodeForPerformance; - if (encoder_rate != 0) - config.encode_interval = - base::TimeDelta::FromSeconds(/*secs=*/1u) / encoder_rate; - - auto video_encoder = - VideoEncoder::Create(config, g_env->GetGpuMemoryBufferFactory(), - std::move(bitstream_processors)); - LOG_ASSERT(video_encoder); - LOG_ASSERT(video_encoder->Initialize(video)); - - return video_encoder; + return bitstream_processors; } scoped_refptr<const VideoFrame> GetModelFrame(size_t frame_index) { @@ -492,10 +505,6 @@ } std::unique_ptr<RawDataHelper> raw_data_helper_; - - PerformanceEvaluator* performance_evaluator_; - SSIMVideoFrameValidator* ssim_validator_; - PSNRVideoFrameValidator* psnr_validator_; }; } // namespace @@ -504,8 +513,8 @@ // performance. This test will encode a video as fast as possible, and gives an // idea about the maximum output of the encoder. TEST_F(VideoEncoderTest, MeasureUncappedPerformance) { - auto encoder = - CreateVideoEncoder(g_env->Video(), g_env->Profile(), g_env->Bitrate()); + auto encoder = CreateVideoEncoder(/*encode_rate=*/absl::nullopt, + /*measure_quality=*/false); encoder->SetEventWaitTimeout(kPerfEventTimeout); performance_evaluator_->StartMeasuring(); @@ -526,8 +535,8 @@ // This test can be used to measure the cpu metrics during encoding. TEST_F(VideoEncoderTest, MeasureCappedPerformance) { const uint32_t kEncodeRate = 30; - auto encoder = CreateVideoEncoder(g_env->Video(), g_env->Profile(), - g_env->Bitrate(), kEncodeRate); + auto encoder = CreateVideoEncoder(/*encode_rate=*/kEncodeRate, + /*measure_quality=*/false); encoder->SetEventWaitTimeout(kPerfEventTimeout); performance_evaluator_->StartMeasuring(); @@ -544,8 +553,8 @@ } TEST_F(VideoEncoderTest, MeasureProducedBitstreamQuality) { - auto encoder = CreateVideoEncoderForQualityPerformance( - g_env->Video(), g_env->Profile(), g_env->Bitrate()); + auto encoder = CreateVideoEncoder(/*encode_rate=*/absl::nullopt, + /*measure_quality=*/true); encoder->SetEventWaitTimeout(kPerfEventTimeout); encoder->Encode();
diff --git a/media/mojo/mojom/video_encode_accelerator.mojom b/media/mojo/mojom/video_encode_accelerator.mojom index a274f81..b1f7a69 100644 --- a/media/mojo/mojom/video_encode_accelerator.mojom +++ b/media/mojo/mojom/video_encode_accelerator.mojom
@@ -178,14 +178,28 @@ Flush() => (bool result); }; +// H264Metadata, Vp8Metadata, Vp9Metadata define mojo transport formats for +// media::H264Metadata, media::Vp8Metadata and media::Vp9Metadata, respectively. +// See the structures defined video_encode_accelerator.h for the descriptions of +// the variables. +// Either of them is filled in GPU process only in the case of temporal/spatial +// SVC encoding. That is, none of them is filled in the case of non +// temporal/spatial SVC encoding. Thus CodecMetadata is union and CodecMetadata +// exists as optional in BitstreamBufferMetadata. +// BitstreamBufferMetadata is metadata about a bitstream buffer produced by a +// hardware encoder. The structure is passed from GPU process to renderer +// process in BitstreamBufferReady() call. +struct H264Metadata { + uint8 temporal_idx; + bool layer_sync; +}; + struct Vp8Metadata { bool non_reference; uint8 temporal_idx; bool layer_sync; }; -// This defines a mojo transport format for media::Vp9Metadata. -// See media::Vp9Metadata for the descriptions of the variables. struct Vp9Metadata { bool has_reference; bool temporal_up_switch; @@ -200,6 +214,7 @@ // Codec specific metadata. union CodecMetadata { + H264Metadata h264; Vp8Metadata vp8; Vp9Metadata vp9; };
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc b/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc index 57dec1c..10c3fd1 100644 --- a/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc +++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
@@ -116,6 +116,9 @@ Read(media::mojom::CodecMetadataDataView data, media::BitstreamBufferMetadata* out) { switch (data.tag()) { + case media::mojom::CodecMetadataDataView::Tag::H264: { + return data.ReadH264(&out->h264); + } case media::mojom::CodecMetadataDataView::Tag::VP8: { return data.ReadVp8(&out->vp8); } @@ -142,6 +145,15 @@ } // static +bool StructTraits<media::mojom::H264MetadataDataView, media::H264Metadata>:: + Read(media::mojom::H264MetadataDataView data, + media::H264Metadata* out_metadata) { + out_metadata->temporal_idx = data.temporal_idx(); + out_metadata->layer_sync = data.layer_sync(); + return true; +} + +// static bool StructTraits<media::mojom::Vp8MetadataDataView, media::Vp8Metadata>::Read( media::mojom::Vp8MetadataDataView data, media::Vp8Metadata* out_metadata) {
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits.h b/media/mojo/mojom/video_encode_accelerator_mojom_traits.h index 6133db3..99bf03b1 100644 --- a/media/mojo/mojom/video_encode_accelerator_mojom_traits.h +++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
@@ -74,7 +74,9 @@ media::BitstreamBufferMetadata> { static media::mojom::CodecMetadataDataView::Tag GetTag( const media::BitstreamBufferMetadata& metadata) { - if (metadata.vp8) { + if (metadata.h264) { + return media::mojom::CodecMetadataDataView::Tag::H264; + } else if (metadata.vp8) { return media::mojom::CodecMetadataDataView::Tag::VP8; } else if (metadata.vp9) { return media::mojom::CodecMetadataDataView::Tag::VP9; @@ -84,14 +86,20 @@ } static bool IsNull(const media::BitstreamBufferMetadata& metadata) { - return !metadata.vp8 && !metadata.vp9; + return !metadata.h264 && !metadata.vp8 && !metadata.vp9; } static void SetToNull(media::BitstreamBufferMetadata* metadata) { + metadata->h264.reset(); metadata->vp8.reset(); metadata->vp9.reset(); } + static const media::H264Metadata& h264( + const media::BitstreamBufferMetadata& metadata) { + return *metadata.h264; + } + static const media::Vp8Metadata& vp8( const media::BitstreamBufferMetadata& metadata) { return *metadata.vp8; @@ -129,6 +137,21 @@ }; template <> +class StructTraits<media::mojom::H264MetadataDataView, media::H264Metadata> { + public: + static uint8_t temporal_idx(const media::H264Metadata& vp8) { + return vp8.temporal_idx; + } + + static bool layer_sync(const media::H264Metadata& vp8) { + return vp8.layer_sync; + } + + static bool Read(media::mojom::H264MetadataDataView data, + media::H264Metadata* out_metadata); +}; + +template <> class StructTraits<media::mojom::Vp8MetadataDataView, media::Vp8Metadata> { public: static bool non_reference(const media::Vp8Metadata& vp8) {
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc b/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc index 373ff6c..0dbc1ca 100644 --- a/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc +++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
@@ -111,6 +111,17 @@ input_metadata, output_metadata)); EXPECT_EQ(input_metadata, output_metadata); + H264Metadata h264; + h264.temporal_idx = 1; + h264.layer_sync = true; + input_metadata.h264 = h264; + output_metadata = ::media::BitstreamBufferMetadata(); + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>( + input_metadata, output_metadata)); + EXPECT_EQ(input_metadata, output_metadata); + input_metadata.h264.reset(); + Vp8Metadata vp8; vp8.non_reference = true; vp8.temporal_idx = 1;
diff --git a/media/mojo/services/mojo_video_encode_accelerator_service.cc b/media/mojo/services/mojo_video_encode_accelerator_service.cc index 07ec05b4..c9a6035 100644 --- a/media/mojo/services/mojo_video_encode_accelerator_service.cc +++ b/media/mojo/services/mojo_video_encode_accelerator_service.cc
@@ -63,6 +63,14 @@ return; } + if (gpu_workarounds_.disable_accelerated_vp9_encode && + config.output_profile >= VP9PROFILE_PROFILE0 && + config.output_profile <= VP9PROFILE_PROFILE3) { + LOG(ERROR) << __func__ << " VP9 encoding disabled by GPU policy"; + std::move(success_callback).Run(false); + return; + } + if (encoder_) { DLOG(ERROR) << __func__ << " VEA is already initialized"; std::move(success_callback).Run(false);
diff --git a/media/video/video_encode_accelerator.cc b/media/video/video_encode_accelerator.cc index e513e22..81f8036 100644 --- a/media/video/video_encode_accelerator.cc +++ b/media/video/video_encode_accelerator.cc
@@ -13,6 +13,10 @@ namespace media { +H264Metadata::H264Metadata() = default; +H264Metadata::~H264Metadata() = default; +H264Metadata::H264Metadata(const H264Metadata&) = default; + Vp8Metadata::Vp8Metadata() : non_reference(false), temporal_idx(0), layer_sync(false) {} @@ -187,6 +191,10 @@ Bitrate::ConstantBitrate(bitrate_allocation.GetSumBps()), framerate); } +bool operator==(const H264Metadata& l, const H264Metadata& r) { + return l.temporal_idx == r.temporal_idx && l.layer_sync == r.layer_sync; +} + bool operator==(const Vp8Metadata& l, const Vp8Metadata& r) { return l.non_reference == r.non_reference && l.temporal_idx == r.temporal_idx && l.layer_sync == r.layer_sync;
diff --git a/media/video/video_encode_accelerator.h b/media/video/video_encode_accelerator.h index 8afb378..7414091 100644 --- a/media/video/video_encode_accelerator.h +++ b/media/video/video_encode_accelerator.h
@@ -30,6 +30,20 @@ class BitstreamBuffer; class VideoFrame; +// Metadata for a H264 bitstream buffer. +// |temporal_idx| indicates the temporal index for this frame. +// |layer_sync| is true iff this frame has |temporal_idx| > 0 and does NOT +// reference any reference buffer containing a frame with +// temporal_idx > 0. +struct MEDIA_EXPORT H264Metadata final { + H264Metadata(); + ~H264Metadata(); + H264Metadata(const H264Metadata&); + + uint8_t temporal_idx = 0; + bool layer_sync = false; +}; + // Metadata for a VP8 bitstream buffer. // |non_reference| is true iff this frame does not update any reference buffer, // meaning dropping this frame still results in a decodable @@ -97,8 +111,9 @@ bool key_frame; base::TimeDelta timestamp; - // Either |vp8| or |vp9| may be set, but not both of them. Presumably, it's - // also possible for none of them to be set. + // |h264|, |vp8| or |vp9| may be set, but not multiple of them. Presumably, + // it's also possible for none of them to be set. + absl::optional<H264Metadata> h264; absl::optional<Vp8Metadata> vp8; absl::optional<Vp9Metadata> vp9; };
diff --git a/media/video/video_encode_accelerator_adapter.cc b/media/video/video_encode_accelerator_adapter.cc index 91c721a..f647948 100644 --- a/media/video/video_encode_accelerator_adapter.cc +++ b/media/video/video_encode_accelerator_adapter.cc
@@ -450,7 +450,9 @@ result.key_frame = metadata.key_frame; result.timestamp = metadata.timestamp; result.size = metadata.payload_size_bytes; - if (metadata.vp9.has_value()) + if (metadata.h264.has_value()) + result.temporal_id = metadata.h264.value().temporal_idx; + else if (metadata.vp9.has_value()) result.temporal_id = metadata.vp9.value().temporal_idx; else if (metadata.vp8.has_value()) result.temporal_id = metadata.vp8.value().temporal_idx;
diff --git a/media/video/video_encode_accelerator_adapter_test.cc b/media/video/video_encode_accelerator_adapter_test.cc index baa9c4f..81add5e 100644 --- a/media/video/video_encode_accelerator_adapter_test.cc +++ b/media/video/video_encode_accelerator_adapter_test.cc
@@ -225,6 +225,8 @@ absl::optional<VideoEncoder::CodecDescription>) { if (output.timestamp == base::TimeDelta::FromMilliseconds(1)) EXPECT_EQ(output.temporal_id, 1); + else if (output.timestamp == base::TimeDelta::FromMilliseconds(2)) + EXPECT_EQ(output.temporal_id, 1); else EXPECT_EQ(output.temporal_id, 2); outputs_count++; @@ -234,6 +236,9 @@ [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) { BitstreamBufferMetadata result(1, keyframe, frame->timestamp()); if (frame->timestamp() == base::TimeDelta::FromMilliseconds(1)) { + result.h264 = H264Metadata(); + result.h264->temporal_idx = 1; + } else if (frame->timestamp() == base::TimeDelta::FromMilliseconds(2)) { result.vp8 = Vp8Metadata(); result.vp8->temporal_idx = 1; } else { @@ -249,11 +254,15 @@ base::TimeDelta::FromMilliseconds(1)); auto frame2 = CreateGreenFrame(options.frame_size, pixel_format, base::TimeDelta::FromMilliseconds(2)); + auto frame3 = CreateGreenFrame(options.frame_size, pixel_format, + base::TimeDelta::FromMilliseconds(3)); adapter()->Encode(frame1, true, ValidatingStatusCB()); RunUntilIdle(); adapter()->Encode(frame2, true, ValidatingStatusCB()); RunUntilIdle(); - EXPECT_EQ(outputs_count, 2); + adapter()->Encode(frame3, true, ValidatingStatusCB()); + RunUntilIdle(); + EXPECT_EQ(outputs_count, 3); } TEST_F(VideoEncodeAcceleratorAdapterTest, FlushDuringInitialize) {
diff --git a/mojo/public/cpp/bindings/struct_traits.h b/mojo/public/cpp/bindings/struct_traits.h index a0f226ad..ca63042 100644 --- a/mojo/public/cpp/bindings/struct_traits.h +++ b/mojo/public/cpp/bindings/struct_traits.h
@@ -98,22 +98,6 @@ // that case, an incoming null value is considered invalid and causes the // message pipe to be disconnected. // -// 4. [Optional] As mentioned above, getters for string/struct/array/map/union -// fields are called multiple times (twice to be exact). If you need to do -// some expensive calculation/conversion, you probably want to cache the -// result across multiple calls. You can introduce an arbitrary context -// object by adding two optional methods: -// static void* SetUpContext(const T& input); -// static void TearDownContext(const T& input, void* context); -// -// And then you append a second parameter, void* context, to getters: -// static <return type> <field name>(const T& input, void* context); -// -// If a T instance is not null, the serialization code will call -// SetUpContext() at the beginning, and pass the resulting context pointer -// to getters. After serialization is done, it calls TearDownContext() so -// that you can do any necessary cleanup. -// // In the description above, methods having an |input| parameter define it as // const reference of T. Actually, it can be a non-const reference of T too. // E.g., if T contains Mojo handles or interfaces whose ownership needs to be
diff --git a/mojo/public/cpp/bindings/union_traits.h b/mojo/public/cpp/bindings/union_traits.h index 243addd..97aa14ba 100644 --- a/mojo/public/cpp/bindings/union_traits.h +++ b/mojo/public/cpp/bindings/union_traits.h
@@ -21,11 +21,10 @@ // 1. Getters for each field in the Mojom type. // 2. Read() method. // 3. [Optional] IsNull() and SetToNull(). -// 4. [Optional] SetUpContext() and TearDownContext(). // Please see the documentation of StructTraits for details of these methods. // // Unlike StructTraits, there is one more method to implement: -// 5. A static GetTag() method indicating which field is the current active +// 4. A static GetTag() method indicating which field is the current active // field for serialization: // // static DataViewType::Tag GetTag(const T& input);
diff --git a/mojo/public/tools/bindings/README.md b/mojo/public/tools/bindings/README.md index bbc5e76..da427e4 100644 --- a/mojo/public/tools/bindings/README.md +++ b/mojo/public/tools/bindings/README.md
@@ -448,7 +448,15 @@ matching `value` in the list of `enabled_features`, the definition will be disabled. This is useful for mojom definitions that only make sense on one platform. Note that the `EnableIf` attribute can only be set once per - definition. + definition and cannot be set at the same time as `EnableIfNot`. + +* **`[EnableIfNot=value]`**: + The `EnableIfNot` attribute is used to conditionally enable definitions when + the mojom is parsed. If the `mojom` target in the GN file includes the + matching `value` in the list of `enabled_features`, the definition will be + disabled. This is useful for mojom definitions that only make sense on all but + one platform. Note that the `EnableIfNot` attribute can only be set once per + definition and cannot be set at the same time as `EnableIf`. * **`[ServiceSandbox=value]`**: The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a
diff --git a/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/mojo/public/tools/mojom/mojom/parse/conditional_features.py index 3cb73c5..ea253c31 100644 --- a/mojo/public/tools/mojom/mojom/parse/conditional_features.py +++ b/mojo/public/tools/mojom/mojom/parse/conditional_features.py
@@ -17,8 +17,10 @@ def _IsEnabled(definition, enabled_features): """Returns true if a definition is enabled. - A definition is enabled if it has no EnableIf attribute, or if the value of - the EnableIf attribute is in enabled_features. + A definition is enabled if it has no EnableIf/EnableIfNot attribute. + It is retained if it has an EnableIf attribute and the attribute is in + enabled_features. It is retained if it has an EnableIfNot attribute and the + attribute is not in enabled features. """ if not hasattr(definition, "attribute_list"): return True @@ -27,17 +29,19 @@ already_defined = False for a in definition.attribute_list: - if a.key == 'EnableIf': + if a.key == 'EnableIf' or a.key == 'EnableIfNot': if already_defined: raise EnableIfError( definition.filename, - "EnableIf attribute may only be defined once per field.", + "EnableIf/EnableIfNot attribute may only be set once per field.", definition.lineno) already_defined = True for attribute in definition.attribute_list: if attribute.key == 'EnableIf' and attribute.value not in enabled_features: return False + if attribute.key == 'EnableIfNot' and attribute.value in enabled_features: + return False return True
diff --git a/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py index aa609be7..de5d48f3 100644 --- a/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py +++ b/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
@@ -55,6 +55,48 @@ """ self.parseAndAssertEqual(const_source, expected_source) + def testFilterIfNotConst(self): + """Test that Consts are correctly filtered.""" + const_source = """ + [EnableIfNot=blue] + const int kMyConst1 = 1; + [EnableIfNot=orange] + const double kMyConst2 = 2; + [EnableIf=blue,orange] + const int kMyConst3 = 3; + [EnableIfNot=blue,orange] + const int kMyConst4 = 4; + [EnableIfNot=purple,orange] + const int kMyConst5 = 5; + """ + expected_source = """ + [EnableIfNot=orange] + const double kMyConst2 = 2; + [EnableIf=blue,orange] + const int kMyConst3 = 3; + [EnableIfNot=purple,orange] + const int kMyConst5 = 5; + """ + self.parseAndAssertEqual(const_source, expected_source) + + def testFilterIfNotMultipleConst(self): + """Test that Consts are correctly filtered.""" + const_source = """ + [EnableIfNot=blue] + const int kMyConst1 = 1; + [EnableIfNot=orange,blue] + const double kMyConst2 = 2; + [EnableIfNot=orange,purple] + const int kMyConst3 = 3; + """ + expected_source = """ + [EnableIfNot=orange,blue] + const double kMyConst2 = 2; + [EnableIfNot=orange,purple] + const int kMyConst3 = 3; + """ + self.parseAndAssertEqual(const_source, expected_source) + def testFilterEnum(self): """Test that EnumValues are correctly filtered from an Enum.""" enum_source = """ @@ -91,6 +133,24 @@ """ self.parseAndAssertEqual(import_source, expected_source) + def testFilterIfNotImport(self): + """Test that imports are correctly filtered from a Mojom.""" + import_source = """ + [EnableIf=blue] + import "foo.mojom"; + [EnableIfNot=purple] + import "bar.mojom"; + [EnableIfNot=green] + import "baz.mojom"; + """ + expected_source = """ + [EnableIf=blue] + import "foo.mojom"; + [EnableIfNot=purple] + import "bar.mojom"; + """ + self.parseAndAssertEqual(import_source, expected_source) + def testFilterInterface(self): """Test that definitions are correctly filtered from an Interface.""" interface_source = """ @@ -175,6 +235,50 @@ """ self.parseAndAssertEqual(struct_source, expected_source) + def testFilterIfNotStruct(self): + """Test that definitions are correctly filtered from a Struct.""" + struct_source = """ + struct MyStruct { + [EnableIf=blue] + enum MyEnum { + VALUE1, + [EnableIfNot=red] + VALUE2, + }; + [EnableIfNot=yellow] + const double kMyConst = 1.23; + [EnableIf=green] + int32 a; + double b; + [EnableIfNot=purple] + int32 c; + [EnableIf=blue] + double d; + int32 e; + [EnableIfNot=red] + double f; + }; + """ + expected_source = """ + struct MyStruct { + [EnableIf=blue] + enum MyEnum { + VALUE1, + }; + [EnableIfNot=yellow] + const double kMyConst = 1.23; + [EnableIf=green] + int32 a; + double b; + [EnableIfNot=purple] + int32 c; + [EnableIf=blue] + double d; + int32 e; + }; + """ + self.parseAndAssertEqual(struct_source, expected_source) + def testFilterUnion(self): """Test that UnionFields are correctly filtered from a Union.""" union_source = """ @@ -228,6 +332,30 @@ conditional_features.RemoveDisabledDefinitions, definition, ENABLED_FEATURES) + def testMultipleEnableIfs(self): + source = """ + enum Foo { + [EnableIf=red,EnableIfNot=yellow] + kBarValue = 5, + }; + """ + definition = parser.Parse(source, "my_file.mojom") + self.assertRaises(conditional_features.EnableIfError, + conditional_features.RemoveDisabledDefinitions, + definition, ENABLED_FEATURES) + + def testMultipleEnableIfs(self): + source = """ + enum Foo { + [EnableIfNot=red,EnableIfNot=yellow] + kBarValue = 5, + }; + """ + definition = parser.Parse(source, "my_file.mojom") + self.assertRaises(conditional_features.EnableIfError, + conditional_features.RemoveDisabledDefinitions, + definition, ENABLED_FEATURES) + if __name__ == '__main__': unittest.main()
diff --git a/mojo/public/tools/mojom/mojom_parser.py b/mojo/public/tools/mojom/mojom_parser.py index 07a7ba5..74beb077 100755 --- a/mojo/public/tools/mojom/mojom_parser.py +++ b/mojo/public/tools/mojom/mojom_parser.py
@@ -274,7 +274,7 @@ module_root_paths: A list of absolute filesystem paths which contain already-generated modules for any non-transitive dependencies. enabled_features: A list of enabled feature names, controlling which AST - nodes are filtered by [EnableIf] attributes. + nodes are filtered by [EnableIf] or [EnableIfNot] attributes. module_metadata: A list of 2-tuples representing metadata key-value pairs to attach to each compiled module output. @@ -433,9 +433,9 @@ help='Enables a named feature when parsing the given mojoms. Features ' 'are identified by arbitrary string values. Specifying this flag with a ' 'given FEATURE name will cause the parser to process any syntax elements ' - 'tagged with an [EnableIf=FEATURE] attribute. If this flag is not ' - 'provided for a given FEATURE, such tagged elements are discarded by the ' - 'parser and will not be present in the compiled output.') + 'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this ' + 'flag is not provided for a given FEATURE, such tagged elements are ' + 'discarded by the parser and will not be present in the compiled output.') arg_parser.add_argument( '--check-imports', dest='build_metadata_filename',
diff --git a/pdf/pdfium/pdfium_page.cc b/pdf/pdfium/pdfium_page.cc index 27ceeed..2d66b6f 100644 --- a/pdf/pdfium/pdfium_page.cc +++ b/pdf/pdfium/pdfium_page.cc
@@ -14,6 +14,7 @@ #include "base/bind.h" #include "base/callback.h" #include "base/check_op.h" +#include "base/cxx17_backports.h" #include "base/metrics/histogram_functions.h" #include "base/numerics/math_constants.h" #include "base/numerics/safe_math.h" @@ -933,7 +934,7 @@ // If `x` < 0, scroll to the left side of the page. // If `x` > page width, scroll to the right side of the page. return TransformPageToScreenX( - std::max(std::min(x, FPDF_GetPageWidthF(GetPage())), 0.0f)); + base::clamp(x, 0.0f, FPDF_GetPageWidthF(GetPage()))); } float PDFiumPage::PreProcessAndTransformInPageCoordY(float y) {
diff --git a/ppapi/shared_impl/BUILD.gn b/ppapi/shared_impl/BUILD.gn index 4118baa..5d1c48b 100644 --- a/ppapi/shared_impl/BUILD.gn +++ b/ppapi/shared_impl/BUILD.gn
@@ -4,36 +4,135 @@ import("//build/config/nacl/config.gni") -component("shared_impl") { - output_name = "ppapi_shared" +# //ppapi/shared_impl and //ppapi/thunk go into the same library. +config("export_shared_impl_and_thunk") { + visibility = [ + ":*", + "//ppapi/thunk:*", + ] + defines = [ + "PPAPI_SHARED_IMPLEMENTATION", + "PPAPI_THUNK_IMPLEMENTATION", + ] +} + +source_set("headers") { + visibility = [ + ":*", + "//ppapi/thunk:*", + ] + + sources = [ + "host_resource.h", + "ppapi_globals.h", + "proxy_lock.h", + "resource.h", + ] + + configs += [ ":export_shared_impl_and_thunk" ] + + deps = [ + "//base", + "//ppapi/c/", + ] +} + +# This contains the things that //ppapi/thunk needs. +source_set("common") { + visibility = [ + ":*", + "//ppapi/thunk:*", + ] sources = [ "array_var.cc", "array_var.h", - "array_writer.cc", - "array_writer.h", "callback_tracker.cc", "callback_tracker.h", "dictionary_var.cc", "dictionary_var.h", + "file_ref_create_info.cc", + "file_ref_create_info.h", + "host_resource.cc", + "id_assignment.cc", + "id_assignment.h", + "ppapi_globals.cc", + "ppb_audio_config_shared.cc", + "ppb_audio_config_shared.h", + "ppb_device_ref_shared.cc", + "ppb_device_ref_shared.h", + "ppb_flash_font_file_shared.h", + "ppb_image_data_shared.cc", + "ppb_image_data_shared.h", + "ppb_message_loop_shared.cc", + "ppb_message_loop_shared.h", + "proxy_lock.cc", + "resource.cc", + "resource_tracker.cc", + "resource_tracker.h", + "resource_var.cc", + "resource_var.h", + "scoped_pp_var.cc", + "scoped_pp_var.h", + "tracked_callback.cc", + "tracked_callback.h", + "url_response_info_data.cc", + "url_response_info_data.h", + "var.cc", + "var.h", + "var_tracker.cc", + "var_tracker.h", + ] + + if (!is_nacl) { + sources += [ + "ppb_url_util_shared.cc", + "ppb_url_util_shared.h", + "private/ppb_char_set_shared.cc", + "private/ppb_char_set_shared.h", + ] + } + + configs += [ ":export_shared_impl_and_thunk" ] + + public_deps = [ ":headers" ] + + deps = [ + "//base", + "//base:i18n", + "//build:chromeos_buildflags", + "//ppapi/c:c", + "//ppapi/thunk:headers", + "//third_party/icu:icuuc", + ] + + if (!is_nacl) { + deps += [ "//skia" ] + } + + if (!is_nacl_nonsfi) { + deps += [ "//url" ] + } +} + +component("shared_impl") { + output_name = "ppapi_shared" + + sources = [ + "array_writer.cc", + "array_writer.h", "file_growth.cc", "file_growth.h", "file_io_state_manager.cc", "file_io_state_manager.h", "file_path.cc", "file_path.h", - "file_ref_create_info.cc", - "file_ref_create_info.h", "file_ref_util.cc", "file_ref_util.h", "file_system_util.cc", "file_system_util.h", "file_type_conversion.cc", "file_type_conversion.h", - "host_resource.cc", - "host_resource.h", - "id_assignment.cc", - "id_assignment.h", "media_stream_audio_track_shared.cc", "media_stream_audio_track_shared.h", "media_stream_buffer.h", @@ -46,8 +145,6 @@ "platform_file.cc", "platform_file.h", "ppapi_constants.h", - "ppapi_globals.cc", - "ppapi_globals.h", "ppapi_nacl_plugin_args.cc", "ppapi_nacl_plugin_args.h", "ppapi_permissions.cc", @@ -56,27 +153,18 @@ "ppapi_preferences.h", "ppapi_switches.cc", "ppapi_switches.h", - "ppb_audio_config_shared.cc", - "ppb_audio_config_shared.h", "ppb_audio_shared.cc", "ppb_audio_shared.h", "ppb_crypto_shared.cc", - "ppb_device_ref_shared.cc", - "ppb_device_ref_shared.h", - "ppb_flash_font_file_shared.h", "ppb_gamepad_shared.cc", "ppb_gamepad_shared.h", "ppb_graphics_3d_shared.cc", "ppb_graphics_3d_shared.h", - "ppb_image_data_shared.cc", - "ppb_image_data_shared.h", "ppb_input_event_shared.cc", "ppb_input_event_shared.h", "ppb_instance_shared.cc", "ppb_instance_shared.h", "ppb_memory_shared.cc", - "ppb_message_loop_shared.cc", - "ppb_message_loop_shared.h", "ppb_opengles2_shared.cc", "ppb_opengles2_shared.h", "ppb_tcp_socket_shared.cc", @@ -89,34 +177,16 @@ "ppb_view_shared.h", "ppp_instance_combined.cc", "ppp_instance_combined.h", - "proxy_lock.cc", - "proxy_lock.h", - "resource.cc", - "resource.h", - "resource_tracker.cc", - "resource_tracker.h", - "resource_var.cc", - "resource_var.h", "scoped_pp_resource.cc", "scoped_pp_resource.h", - "scoped_pp_var.cc", - "scoped_pp_var.h", "socket_option_data.cc", "socket_option_data.h", "thread_aware_callback.cc", "thread_aware_callback.h", "time_conversion.cc", "time_conversion.h", - "tracked_callback.cc", - "tracked_callback.h", "url_request_info_data.cc", "url_request_info_data.h", - "url_response_info_data.cc", - "url_response_info_data.h", - "var.cc", - "var.h", - "var_tracker.cc", - "var_tracker.h", "vpn_provider_util.cc", "vpn_provider_util.h", @@ -130,12 +200,8 @@ if (!is_nacl) { sources += [ - "ppb_url_util_shared.cc", - "ppb_url_util_shared.h", "ppb_video_decoder_shared.cc", "ppb_video_decoder_shared.h", - "private/ppb_char_set_shared.cc", - "private/ppb_char_set_shared.h", "private/ppb_x509_util_shared.cc", "private/ppb_x509_util_shared.h", ] @@ -153,15 +219,13 @@ ] } - configs += [ "//build/config:precompiled_headers" ] - defines = [ - "PPAPI_SHARED_IMPLEMENTATION", - - # This target goes in the same library as thunk (in GYP they are the same). - "PPAPI_THUNK_IMPLEMENTATION", + configs += [ + ":export_shared_impl_and_thunk", + "//build/config:precompiled_headers", ] public_deps = [ + ":common", "//base", "//ppapi/c", "//ppapi/thunk", @@ -181,19 +245,13 @@ if (!is_nacl) { deps += [ "//net", - "//skia", "//ui/events:events_base", "//ui/surface", ] } if (!is_nacl_nonsfi) { - deps += [ - "//base:i18n", - "//base/third_party/dynamic_annotations", - "//third_party/icu:icuuc", - "//url", - ] + deps += [ "//base/third_party/dynamic_annotations" ] } if (is_mac) {
diff --git a/ppapi/thunk/BUILD.gn b/ppapi/thunk/BUILD.gn index b353f80..cd0d1da 100644 --- a/ppapi/thunk/BUILD.gn +++ b/ppapi/thunk/BUILD.gn
@@ -2,17 +2,13 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -source_set("thunk") { - # In GYP this is the same target as shared_impl. In GN these are split apart - # for clarity but to get component builds correct, targets must only depend - # on these via the shared_impl component. - # TODO(brettw) separate these when GYP compat is no longer required. - visibility = [ "//ppapi/shared_impl" ] - +source_set("headers") { + visibility = [ + ":thunk", + "//ppapi/shared_impl:common", + ] sources = [ - "enter.cc", "enter.h", - "interfaces_legacy.h", "interfaces_postamble.h", "interfaces_ppb_private.h", "interfaces_ppb_private_flash.h", @@ -23,10 +19,33 @@ "interfaces_ppb_public_stable.h", "interfaces_preamble.h", "ppapi_thunk_export.h", + "ppb_audio_config_api.h", + "ppb_device_ref_api.h", + "ppb_instance_api.h", + "ppb_message_loop_api.h", + "resource_creation_api.h", + "thunk.h", + ] + + configs += [ "//ppapi/shared_impl:export_shared_impl_and_thunk" ] + + deps = [ + "//base", + "//gpu/command_buffer/common", + "//ppapi/c", + "//ppapi/shared_impl:headers", + ] +} + +source_set("thunk") { + visibility = [ "//ppapi/shared_impl" ] + + sources = [ + "enter.cc", + "interfaces_legacy.h", "ppb_audio_api.h", "ppb_audio_buffer_api.h", "ppb_audio_buffer_thunk.cc", - "ppb_audio_config_api.h", "ppb_audio_config_thunk.cc", "ppb_audio_input_api.h", "ppb_audio_output_api.h", @@ -40,7 +59,6 @@ "ppb_camera_device_private_thunk.cc", "ppb_console_thunk.cc", "ppb_cursor_control_thunk.cc", - "ppb_device_ref_api.h", "ppb_device_ref_dev_thunk.cc", "ppb_ext_crx_file_system_private_thunk.cc", "ppb_file_chooser_api.h", @@ -71,7 +89,6 @@ "ppb_image_data_thunk.cc", "ppb_input_event_api.h", "ppb_input_event_thunk.cc", - "ppb_instance_api.h", "ppb_instance_private_thunk.cc", "ppb_instance_thunk.cc", "ppb_isolated_file_system_private_api.h", @@ -80,7 +97,6 @@ "ppb_media_stream_audio_track_thunk.cc", "ppb_media_stream_video_track_api.h", "ppb_media_stream_video_track_thunk.cc", - "ppb_message_loop_api.h", "ppb_messaging_thunk.cc", "ppb_mouse_cursor_thunk.cc", "ppb_mouse_lock_thunk.cc", @@ -132,8 +148,6 @@ "ppb_vpn_provider_thunk.cc", "ppb_websocket_api.h", "ppb_websocket_thunk.cc", - "resource_creation_api.h", - "thunk.h", ] if (!is_nacl) { @@ -169,14 +183,17 @@ ] } - configs += [ "//build/config:precompiled_headers" ] - defines = [ - # This target goes in the same library as shared_impl (in GYP they are the - # same). - "PPAPI_SHARED_IMPLEMENTATION", - - "PPAPI_THUNK_IMPLEMENTATION", + configs += [ + "//ppapi/shared_impl:export_shared_impl_and_thunk", + "//build/config:precompiled_headers", ] - public_deps = [ "//base" ] + deps = [ + "//base", + "//gpu/command_buffer/common", + "//ppapi/c", + "//ppapi/shared_impl:common", + "//ppapi/shared_impl:headers", + ] + public_deps = [ ":headers" ] }
diff --git a/ppapi/thunk/enter.cc b/ppapi/thunk/enter.cc index b03ca24..b774cdd 100644 --- a/ppapi/thunk/enter.cc +++ b/ppapi/thunk/enter.cc
@@ -10,6 +10,7 @@ #include "base/strings/stringprintf.h" #include "base/synchronization/lock.h" #include "ppapi/shared_impl/ppapi_globals.h" +#include "ppapi/shared_impl/resource_tracker.h" #include "ppapi/shared_impl/tracked_callback.h" #include "ppapi/thunk/ppb_instance_api.h" #include "ppapi/thunk/resource_creation_api.h"
diff --git a/ppapi/thunk/enter.h b/ppapi/thunk/enter.h index 25e7935..470ecd2 100644 --- a/ppapi/thunk/enter.h +++ b/ppapi/thunk/enter.h
@@ -14,14 +14,15 @@ #include "ppapi/shared_impl/ppapi_globals.h" #include "ppapi/shared_impl/proxy_lock.h" #include "ppapi/shared_impl/resource.h" -#include "ppapi/shared_impl/resource_tracker.h" #include "ppapi/shared_impl/singleton_resource_id.h" -#include "ppapi/shared_impl/tracked_callback.h" #include "ppapi/thunk/ppapi_thunk_export.h" #include "ppapi/thunk/ppb_instance_api.h" #include "ppapi/thunk/resource_creation_api.h" namespace ppapi { + +class TrackedCallback; + namespace thunk { // Enter* helper objects: These objects wrap a call from the C PPAPI into
diff --git a/ppapi/thunk/ppb_instance_api.h b/ppapi/thunk/ppb_instance_api.h index 7b8c6b2..157dc86 100644 --- a/ppapi/thunk/ppb_instance_api.h +++ b/ppapi/thunk/ppb_instance_api.h
@@ -22,7 +22,6 @@ #include "ppapi/c/ppb_text_input_controller.h" #include "ppapi/c/private/ppb_instance_private.h" #include "ppapi/shared_impl/api_id.h" -#include "ppapi/shared_impl/resource.h" #include "ppapi/shared_impl/singleton_resource_id.h" // Windows headers interfere with this file.
diff --git a/ppapi/thunk/resource_creation_api.h b/ppapi/thunk/resource_creation_api.h index eeef46e..30a2681 100644 --- a/ppapi/thunk/resource_creation_api.h +++ b/ppapi/thunk/resource_creation_api.h
@@ -25,7 +25,6 @@ #include "ppapi/c/ppb_websocket.h" #include "ppapi/c/private/pp_private_font_charset.h" #include "ppapi/shared_impl/api_id.h" -#include "ppapi/shared_impl/ppb_image_data_shared.h" // Windows defines 'PostMessage', so we have to undef it. #ifdef PostMessage
diff --git a/remoting/host/installer/mac/uninstaller/remoting_uninstaller.h b/remoting/host/installer/mac/uninstaller/remoting_uninstaller.h index 3a3b8ca..365a8a6e 100644 --- a/remoting/host/installer/mac/uninstaller/remoting_uninstaller.h +++ b/remoting/host/installer/mac/uninstaller/remoting_uninstaller.h
@@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_H_ +#define REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_H_ + #import <Cocoa/Cocoa.h> @interface RemotingUninstaller : NSObject { @@ -10,3 +13,5 @@ - (OSStatus)remotingUninstall; @end + +#endif // REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_H_ \ No newline at end of file
diff --git a/remoting/host/installer/mac/uninstaller/remoting_uninstaller_app.h b/remoting/host/installer/mac/uninstaller/remoting_uninstaller_app.h index 5346c6d..880a75a9 100644 --- a/remoting/host/installer/mac/uninstaller/remoting_uninstaller_app.h +++ b/remoting/host/installer/mac/uninstaller/remoting_uninstaller_app.h
@@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_APP_H_ +#define REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_APP_H_ + #import <Cocoa/Cocoa.h> @interface RemotingUninstallerAppDelegate : NSObject { @@ -12,3 +15,5 @@ - (IBAction)handleMenuClose:(NSMenuItem*)sender; @end + +#endif // REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_APP_H_ \ No newline at end of file
diff --git a/remoting/tools/winext/manifest/rdp.h b/remoting/tools/winext/manifest/rdp.h index 698af9a8..63ab638 100644 --- a/remoting/tools/winext/manifest/rdp.h +++ b/remoting/tools/winext/manifest/rdp.h
@@ -13,6 +13,9 @@ module MSTSCAX.DLL: category rdp: +#ifndef REMOTING_TOOLS_WINEXT_MANIFEST_RDP_H_ +#define REMOTING_TOOLS_WINEXT_MANIFEST_RDP_H_ + // // GUIDs // @@ -846,3 +849,5 @@ }; HRESULT DllGetClassObject(REFCLSID rclsid, [iid] REFIID riid, [out] COM_INTERFACE_PTR* ppv); + +#endif // REMOTING_TOOLS_WINEXT_MANIFEST_RDP_H_ \ No newline at end of file
diff --git a/services/device/BUILD.gn b/services/device/BUILD.gn index d1956023..3468be4 100644 --- a/services/device/BUILD.gn +++ b/services/device/BUILD.gn
@@ -356,8 +356,7 @@ } # UsbContext is a libusb-specific object. - # TODO(https://crbug.com/1096743) Remove these tests. - if (is_mac) { + if (is_mac || is_win) { sources += [ "usb/usb_context_unittest.cc" ] deps += [ "//third_party/libusb" ] }
diff --git a/services/device/usb/BUILD.gn b/services/device/usb/BUILD.gn index bb40dc9..348e922 100644 --- a/services/device/usb/BUILD.gn +++ b/services/device/usb/BUILD.gn
@@ -62,14 +62,10 @@ "usb_service_android.cc", "usb_service_android.h", ] - - deps += [ ":jni_headers" ] } if (is_win) { sources += [ - "scoped_winusb_handle.cc", - "scoped_winusb_handle.h", "usb_device_handle_win.cc", "usb_device_handle_win.h", "usb_device_win.cc", @@ -77,13 +73,6 @@ "usb_service_win.cc", "usb_service_win.h", ] - - libs = [ - "setupapi.lib", - "winusb.lib", - ] - - deps += [ "//third_party/re2" ] } if (is_mac) { @@ -95,9 +84,30 @@ "usb_service_mac.cc", "usb_service_mac.h", ] + } - # These sources and deps are required for libusb. - # TODO(https://crbug.com/1096743) Remove these sources. + if (is_linux || is_chromeos) { + sources += [ + "usb_device_linux.cc", + "usb_device_linux.h", + ] + } + + if (use_udev) { + if (is_linux || is_chromeos) { + sources += [ + "usb_service_linux.cc", + "usb_service_linux.h", + ] + } + deps += [ "//device/udev_linux" ] + } + + if (is_android) { + deps += [ ":jni_headers" ] + } + + if (is_win || is_mac) { sources += [ "scoped_libusb_device_handle.cc", "scoped_libusb_device_handle.h", @@ -118,21 +128,18 @@ deps += [ "//third_party/libusb" ] } - if (is_linux || is_chromeos) { + if (is_win) { sources += [ - "usb_device_linux.cc", - "usb_device_linux.h", + "scoped_winusb_handle.cc", + "scoped_winusb_handle.h", ] - } - if (use_udev) { - if (is_linux || is_chromeos) { - sources += [ - "usb_service_linux.cc", - "usb_service_linux.h", - ] - } - deps += [ "//device/udev_linux" ] + libs = [ + "setupapi.lib", + "winusb.lib", + ] + + deps += [ "//third_party/re2" ] } if (is_android || is_chromeos || is_linux) {
diff --git a/services/device/usb/usb_context_unittest.cc b/services/device/usb/usb_context_unittest.cc index 0148827e..6f1faee 100644 --- a/services/device/usb/usb_context_unittest.cc +++ b/services/device/usb/usb_context_unittest.cc
@@ -5,6 +5,7 @@ #include "services/device/usb/usb_context.h" #include "base/macros.h" #include "base/threading/platform_thread.h" +#include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/libusb/src/libusb/libusb.h" @@ -27,7 +28,17 @@ } // namespace -TEST_F(UsbContextTest, GracefulShutdown) { +#if defined(OS_LINUX) || defined(OS_CHROMEOS) +// Linux trybot does not support usb. +#define MAYBE_GracefulShutdown DISABLED_GracefulShutdown +#elif defined(OS_ANDROID) +// Android build does not include usb support. +#define MAYBE_GracefulShutdown DISABLED_GracefulShutdown +#else +#define MAYBE_GracefulShutdown GracefulShutdown +#endif + +TEST_F(UsbContextTest, MAYBE_GracefulShutdown) { base::TimeTicks start = base::TimeTicks::Now(); { PlatformUsbContext platform_context;
diff --git a/services/device/usb/usb_service.cc b/services/device/usb/usb_service.cc index 7c23cce..f3a8da62 100644 --- a/services/device/usb/usb_service.cc +++ b/services/device/usb/usb_service.cc
@@ -23,12 +23,14 @@ #include "services/device/usb/usb_service_android.h" #elif defined(USE_UDEV) #include "services/device/usb/usb_service_linux.h" -#elif defined(OS_MAC) -#include "services/device/usb/usb_service_impl.h" +#else +#if defined(OS_MAC) #include "services/device/usb/usb_service_mac.h" #elif defined(OS_WIN) #include "services/device/usb/usb_service_win.h" #endif +#include "services/device/usb/usb_service_impl.h" +#endif namespace device { @@ -53,7 +55,10 @@ #elif defined(USE_UDEV) return base::WrapUnique(new UsbServiceLinux()); #elif defined(OS_WIN) - return base::WrapUnique(new UsbServiceWin()); + if (base::FeatureList::IsEnabled(kNewUsbBackend)) + return base::WrapUnique(new UsbServiceWin()); + else + return base::WrapUnique(new UsbServiceImpl()); #elif defined(OS_MAC) if (base::FeatureList::IsEnabled(kNewUsbBackend)) return base::WrapUnique(new UsbServiceMac());
diff --git a/services/device/usb/usb_service_impl.cc b/services/device/usb/usb_service_impl.cc index 74bfe41d..0b38f32 100644 --- a/services/device/usb/usb_service_impl.cc +++ b/services/device/usb/usb_service_impl.cc
@@ -31,6 +31,16 @@ #include "services/device/usb/webusb_descriptors.h" #include "third_party/libusb/src/libusb/libusb.h" +#if defined(OS_WIN) +#define INITGUID +#include <devpkey.h> +#include <setupapi.h> +#include <usbiodef.h> + +#include "base/strings/string_util.h" +#include "device/base/device_info_query_win.h" +#endif // OS_WIN + namespace device { namespace { @@ -38,6 +48,42 @@ // Standard USB requests and descriptor types: const uint16_t kUsbVersion2_1 = 0x0210; +#if defined(OS_WIN) + +bool IsWinUsbInterface(const std::wstring& device_path) { + DeviceInfoQueryWin device_info_query; + if (!device_info_query.device_info_list_valid()) { + USB_PLOG(ERROR) << "Failed to create a device information set"; + return false; + } + + // This will add the device so we can query driver info. + if (!device_info_query.AddDevice(device_path)) { + USB_PLOG(ERROR) << "Failed to get device interface data for " + << device_path; + return false; + } + + if (!device_info_query.GetDeviceInfo()) { + USB_PLOG(ERROR) << "Failed to get device info for " << device_path; + return false; + } + + std::string buffer; + if (!device_info_query.GetDeviceStringProperty(DEVPKEY_Device_Service, + &buffer)) { + USB_PLOG(ERROR) << "Failed to get device service property"; + return false; + } + + USB_LOG(DEBUG) << "Driver for " << device_path << " is " << buffer << "."; + if (base::StartsWith(buffer, "WinUSB", base::CompareCase::INSENSITIVE_ASCII)) + return true; + return false; +} + +#endif // OS_WIN + scoped_refptr<UsbContext> InitializeUsbContextBlocking() { PlatformUsbContext platform_context = nullptr; int rv = libusb_init(&platform_context); @@ -51,10 +97,21 @@ } absl::optional<std::vector<ScopedLibusbDeviceRef>> GetDeviceListBlocking( + const std::wstring& new_device_path, scoped_refptr<UsbContext> usb_context) { base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); +#if defined(OS_WIN) + if (!new_device_path.empty()) { + if (!IsWinUsbInterface(new_device_path)) { + // Wait to call libusb_get_device_list until libusb will be able to find + // a WinUSB interface for the device. + return absl::nullopt; + } + } +#endif // defined(OS_WIN) + libusb_device** platform_devices = NULL; const ssize_t device_count = libusb_get_device_list(usb_context->context(), &platform_devices); @@ -184,7 +241,7 @@ UsbServiceImpl::~UsbServiceImpl() { NotifyWillDestroyUsbService(); - if (context_) + if (hotplug_enabled_) libusb_hotplug_deregister_callback(context_->context(), hotplug_handle_); } @@ -198,14 +255,40 @@ return; } - if (enumeration_in_progress_) { + if (hotplug_enabled_ && !enumeration_in_progress_) { + // The device list is updated live when hotplug events are supported. + UsbService::GetDevices(std::move(callback)); + } else { pending_enumeration_callbacks_.push_back(std::move(callback)); - return; + RefreshDevices(); } - - UsbService::GetDevices(std::move(callback)); } +#if defined(OS_WIN) + +void UsbServiceImpl::OnDeviceAdded(const GUID& class_guid, + const std::wstring& device_path) { + // Only the root node of a composite USB device has the class GUID + // GUID_DEVINTERFACE_USB_DEVICE but we want to wait until WinUSB is loaded. + // This first pass filter will catch anything that's sitting on the USB bus + // (including devices on 3rd party USB controllers) to avoid the more + // expensive driver check that needs to be done on the FILE thread. + if (device_path.find(L"usb") != std::wstring::npos) { + pending_path_enumerations_.push(device_path); + RefreshDevices(); + } +} + +void UsbServiceImpl::OnDeviceRemoved(const GUID& class_guid, + const std::wstring& device_path) { + // The root USB device node is removed last. + if (class_guid == GUID_DEVINTERFACE_USB_DEVICE) { + RefreshDevices(); + } +} + +#endif // OS_WIN + void UsbServiceImpl::OnUsbContext(scoped_refptr<UsbContext> context) { if (!context) { usb_unavailable_ = true; @@ -221,27 +304,37 @@ static_cast<libusb_hotplug_flag>(0), LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, &UsbServiceImpl::HotplugCallback, this, &hotplug_handle_); - if (rv != LIBUSB_SUCCESS) { - usb_unavailable_ = true; - context_.reset(); - return; - } + if (rv == LIBUSB_SUCCESS) + hotplug_enabled_ = true; // This will call any enumeration callbacks queued while initializing. RefreshDevices(); + +#if defined(OS_WIN) + DeviceMonitorWin* device_monitor = DeviceMonitorWin::GetForAllInterfaces(); + if (device_monitor) + device_observation_.Observe(device_monitor); +#endif // OS_WIN } void UsbServiceImpl::RefreshDevices() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(context_); - DCHECK(!enumeration_in_progress_); + + if (!context_ || enumeration_in_progress_) + return; enumeration_in_progress_ = true; DCHECK(devices_being_enumerated_.empty()); + std::wstring device_path; + if (!pending_path_enumerations_.empty()) { + device_path = pending_path_enumerations_.front(); + pending_path_enumerations_.pop(); + } + base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, kBlockingTaskTraits, - base::BindOnce(&GetDeviceListBlocking, context_), + base::BindOnce(&GetDeviceListBlocking, device_path, context_), base::BindOnce(&UsbServiceImpl::OnDeviceList, weak_factory_.GetWeakPtr())); } @@ -322,6 +415,10 @@ for (GetDevicesCallback& callback : callbacks) std::move(callback).Run(result); } + + if (!pending_path_enumerations_.empty()) { + RefreshDevices(); + } } void UsbServiceImpl::EnumerateDevice(ScopedLibusbDeviceRef platform_device,
diff --git a/services/device/usb/usb_service_impl.h b/services/device/usb/usb_service_impl.h index fcb24e4..21edd890 100644 --- a/services/device/usb/usb_service_impl.h +++ b/services/device/usb/usb_service_impl.h
@@ -24,6 +24,11 @@ #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/libusb/src/libusb/libusb.h" +#if defined(OS_WIN) +#include "base/scoped_observation.h" +#include "device/base/device_monitor_win.h" +#endif // OS_WIN + struct libusb_context; namespace device { @@ -32,7 +37,11 @@ class UsbDeviceImpl; -class UsbServiceImpl final : public UsbService { +class UsbServiceImpl final : +#if defined(OS_WIN) + public DeviceMonitorWin::Observer, +#endif // OS_WIN + public UsbService { public: UsbServiceImpl(); ~UsbServiceImpl() override; @@ -41,6 +50,14 @@ // device::UsbService implementation void GetDevices(GetDevicesCallback callback) override; +#if defined(OS_WIN) + // device::DeviceMonitorWin::Observer implementation + void OnDeviceAdded(const GUID& class_guid, + const std::wstring& device_path) override; + void OnDeviceRemoved(const GUID& class_guid, + const std::wstring& device_path) override; +#endif // OS_WIN + void OnUsbContext(scoped_refptr<UsbContext> context); // Enumerate USB devices from OS and update devices_ map. @@ -77,11 +94,14 @@ // When available the device list will be updated when new devices are // connected instead of only when a full enumeration is requested. + // TODO(reillyg): Support this on all platforms. crbug.com/411715 + bool hotplug_enabled_ = false; libusb_hotplug_callback_handle hotplug_handle_; // Enumeration callbacks are queued until an enumeration completes. bool enumeration_ready_ = false; bool enumeration_in_progress_ = false; + base::queue<std::wstring> pending_path_enumerations_; std::vector<GetDevicesCallback> pending_enumeration_callbacks_; // The map from libusb_device to UsbDeviceImpl. The key is a weak pointer to @@ -99,6 +119,11 @@ // UsbDeviceImpl. std::set<libusb_device*> devices_being_enumerated_; +#if defined(OS_WIN) + base::ScopedObservation<DeviceMonitorWin, DeviceMonitorWin::Observer> + device_observation_{this}; +#endif // OS_WIN + // This WeakPtr is used to safely post hotplug events back to the thread this // object lives on. base::WeakPtr<UsbServiceImpl> weak_self_;
diff --git a/services/device/usb/usb_service_unittest.cc b/services/device/usb/usb_service_unittest.cc index ada5b04..971ec7d4 100644 --- a/services/device/usb/usb_service_unittest.cc +++ b/services/device/usb/usb_service_unittest.cc
@@ -54,7 +54,7 @@ } } -#if defined(OS_MAC) +#if defined(OS_WIN) TEST_F(UsbServiceTest, GetDevicesNewBackend) { base::test::ScopedFeatureList features; features.InitAndEnableFeature(device::kNewUsbBackend); @@ -66,7 +66,7 @@ loop.Run(); } } -#endif // defined(OS_MAC) +#endif // defined(OS_WIN) TEST_F(UsbServiceTest, ClaimGadget) { if (!UsbTestGadget::IsTestEnabled() || !usb_service_)
diff --git a/services/device/usb/usb_service_win.cc b/services/device/usb/usb_service_win.cc index 554c351..3b68c55 100644 --- a/services/device/usb/usb_service_win.cc +++ b/services/device/usb/usb_service_win.cc
@@ -4,16 +4,14 @@ #include "services/device/usb/usb_service_win.h" -// windows.h must be included first. -#include <windows.h> - -#define INITGUID - -#include <devpkey.h> #include <objbase.h> #include <setupapi.h> #include <stdint.h> #include <usbiodef.h> +#include "base/strings/string_piece_forward.h" + +#define INITGUID +#include <devpkey.h> #include "base/bind.h" #include "base/containers/contains.h" @@ -23,7 +21,6 @@ #include "base/scoped_generic.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_number_conversions.h" -#include "base/strings/string_piece_forward.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/sys_string_conversions.h"
diff --git a/storage/browser/blob/blob_url_utils.h b/storage/browser/blob/blob_url_utils.h index 4c125ecf..de742e2 100644 --- a/storage/browser/blob/blob_url_utils.h +++ b/storage/browser/blob/blob_url_utils.h
@@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef STORAGE_BROWSER_BLOB_BLOB_URL_UTILS_H_ +#define STORAGE_BROWSER_BLOB_BLOB_URL_UTILS_H_ + #include "url/gurl.h" namespace storage { @@ -21,3 +24,5 @@ } // namespace BlobUrlUtils } // namespace storage + +#endif // STORAGE_BROWSER_BLOB_BLOB_URL_UTILS_H_ \ No newline at end of file
diff --git a/storage/browser/quota/quota_callbacks.h b/storage/browser/quota/quota_callbacks.h index d7002097..36013d11 100644 --- a/storage/browser/quota/quota_callbacks.h +++ b/storage/browser/quota/quota_callbacks.h
@@ -42,9 +42,11 @@ using AvailableSpaceCallback = base::OnceCallback<void(blink::mojom::QuotaStatusCode, int64_t)>; using StatusCallback = base::OnceCallback<void(blink::mojom::QuotaStatusCode)>; -using GetStorageKeysCallback = - base::OnceCallback<void(const std::set<blink::StorageKey>& storage_keys, +using GetBucketsCallback = + base::OnceCallback<void(const std::set<BucketInfo>& buckets, blink::mojom::StorageType type)>; +using GetStorageKeysCallback = + base::OnceCallback<void(const std::set<blink::StorageKey>& storage_keys)>; using GetUsageInfoCallback = base::OnceCallback<void(UsageInfoEntries)>; using GetBucketCallback = base::OnceCallback<void(const absl::optional<BucketInfo>& bucket_info)>;
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc index 2d2baeac..ca3143e7 100644 --- a/storage/browser/quota/quota_database.cc +++ b/storage/browser/quota/quota_database.cc
@@ -482,11 +482,10 @@ return false; static constexpr char kSql[] = - "DELETE FROM buckets WHERE origin = ? AND type = ? AND name = ?"; + "DELETE FROM buckets WHERE origin = ? AND type = ?"; sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); statement.BindString(0, storage_key.Serialize()); statement.BindInt(1, static_cast<int>(type)); - statement.BindString(2, kDefaultBucketName); if (!statement.Run()) return false; @@ -564,57 +563,44 @@ return QuotaError::kNotFound; } -bool QuotaDatabase::GetStorageKeysModifiedBetween( - StorageType type, - std::set<StorageKey>* storage_keys, - base::Time begin, - base::Time end) { +QuotaErrorOr<std::set<StorageKey>> QuotaDatabase::GetStorageKeysForType( + StorageType type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(storage_keys); - if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone) - return false; + QuotaError open_error = LazyOpen(LazyOpenMode::kFailIfNotFound); + if (open_error != QuotaError::kNone) + return open_error; - DCHECK(!begin.is_max()); - DCHECK(end != base::Time()); static constexpr char kSql[] = - // clang-format off - "SELECT origin FROM buckets " - "WHERE type = ? AND name = ?" - "AND last_modified >= ? AND last_modified < ?"; - // clang-format on + "SELECT DISTINCT origin FROM buckets WHERE type = ?"; sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); statement.BindInt(0, static_cast<int>(type)); - statement.BindString(1, kDefaultBucketName); - statement.BindTime(2, begin); - statement.BindTime(3, end); - storage_keys->clear(); + std::set<StorageKey> storage_keys; while (statement.Step()) { absl::optional<StorageKey> read_storage_key = StorageKey::Deserialize(statement.ColumnString(0)); if (!read_storage_key.has_value()) continue; - storage_keys->insert(std::move(read_storage_key).value()); + storage_keys.insert(read_storage_key.value()); } - - return statement.Succeeded(); + return storage_keys; } -bool QuotaDatabase::GetBucketsModifiedBetween(StorageType type, - std::set<BucketId>* bucket_ids, - base::Time begin, - base::Time end) { +QuotaErrorOr<std::set<BucketInfo>> QuotaDatabase::GetBucketsModifiedBetween( + StorageType type, + base::Time begin, + base::Time end) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(bucket_ids); - if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone) - return false; + QuotaError open_error = LazyOpen(LazyOpenMode::kFailIfNotFound); + if (open_error != QuotaError::kNone) + return open_error; DCHECK(!begin.is_max()); DCHECK(end != base::Time()); + // clang-format off static constexpr char kSql[] = - // clang-format off - "SELECT id FROM buckets " + "SELECT id, origin, name, expiration, quota FROM buckets " "WHERE type = ? AND last_modified >= ? AND last_modified < ?"; // clang-format on @@ -623,11 +609,18 @@ statement.BindTime(1, begin); statement.BindTime(2, end); - bucket_ids->clear(); - while (statement.Step()) - bucket_ids->insert(BucketId(statement.ColumnInt64(0))); - - return statement.Succeeded(); + std::set<BucketInfo> buckets; + while (statement.Step()) { + absl::optional<StorageKey> read_storage_key = + StorageKey::Deserialize(statement.ColumnString(1)); + if (!read_storage_key.has_value()) + continue; + buckets.emplace( + BucketInfo(BucketId(statement.ColumnInt64(0)), read_storage_key.value(), + type, statement.ColumnString(2), statement.ColumnTime(3), + statement.ColumnInt(4))); + } + return buckets; } bool QuotaDatabase::IsStorageKeyDatabaseBootstrapped() {
diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h index 1bad41f5..9091dee 100644 --- a/storage/browser/quota/quota_database.h +++ b/storage/browser/quota/quota_database.h
@@ -147,8 +147,7 @@ // origin bucket can be found. bool GetBucketInfo(BucketId bucket_id, BucketTableEntry* entry); - // TODO(crbug.com/1202167): Remove once all usages have been updated to use - // DeleteBucketInfo. Deletes the default bucket for `storage_key`. + // Removes all buckets for `storage_key` with `type`. bool DeleteStorageKeyInfo(const blink::StorageKey& storage_key, blink::mojom::StorageType type); @@ -167,21 +166,16 @@ const std::set<BucketId>& bucket_exceptions, SpecialStoragePolicy* special_storage_policy); - // TODO(crbug.com/1202167): Remove once all usages have been updated to use - // GetBucketsModifiedBetween. Populates `storage_keys` with the ones that have - // had their default bucket modified since the `begin` and until the `end`. - // Returns whether the operation succeeded. - bool GetStorageKeysModifiedBetween(blink::mojom::StorageType type, - std::set<blink::StorageKey>* storage_keys, - base::Time begin, - base::Time end); + // Returns all storage keys for `type` in the bucket database. + QuotaErrorOr<std::set<blink::StorageKey>> GetStorageKeysForType( + blink::mojom::StorageType type); - // Populates `bucket_ids` with the buckets that have been modified since the - // `begin` and until the `end`. Returns whether the operation succeeded. - bool GetBucketsModifiedBetween(blink::mojom::StorageType type, - std::set<BucketId>* bucket_ids, - base::Time begin, - base::Time end); + // Returns a set of buckets that have been modified since the `begin` and + // until the `end`. Returns a QuotaError if the operations has failed. + QuotaErrorOr<std::set<BucketInfo>> GetBucketsModifiedBetween( + blink::mojom::StorageType type, + base::Time begin, + base::Time end); // Returns false if SetStorageKeyDatabaseBootstrapped has never // been called before, which means existing storage keys may not have been
diff --git a/storage/browser/quota/quota_database_unittest.cc b/storage/browser/quota/quota_database_unittest.cc index b2d685c..6dd0e80a 100644 --- a/storage/browser/quota/quota_database_unittest.cc +++ b/storage/browser/quota/quota_database_unittest.cc
@@ -11,6 +11,7 @@ #include "base/bind.h" #include "base/callback.h" +#include "base/containers/contains.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/test/task_environment.h" @@ -298,6 +299,40 @@ } #endif // !defined(OS_FUCHSIA) +TEST_P(QuotaDatabaseTest, DeleteStorageKeyInfo) { + QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); + EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound)); + + const StorageKey storage_key = + StorageKey::CreateFromStringForTesting("http://example-a/"); + QuotaErrorOr<BucketInfo> temp_bucket1 = + db.CreateBucketForTesting(storage_key, "temp1", kTemp); + QuotaErrorOr<BucketInfo> temp_bucket2 = + db.CreateBucketForTesting(storage_key, "temp2", kTemp); + QuotaErrorOr<BucketInfo> perm_bucket = + db.CreateBucketForTesting(storage_key, "perm", kPerm); + + db.DeleteStorageKeyInfo(storage_key, kTemp); + + QuotaErrorOr<BucketInfo> result = + db.GetBucket(storage_key, temp_bucket1->name, kTemp); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.error(), QuotaError::kNotFound); + + result = db.GetBucket(storage_key, temp_bucket2->name, kTemp); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.error(), QuotaError::kNotFound); + + result = db.GetBucket(storage_key, perm_bucket->name, kPerm); + ASSERT_TRUE(result.ok()); + + db.DeleteStorageKeyInfo(storage_key, kPerm); + + result = db.GetBucket(storage_key, perm_bucket->name, kPerm); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.error(), QuotaError::kNotFound); +} + TEST_P(QuotaDatabaseTest, BucketLastAccessTimeLRU) { QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound)); @@ -408,193 +443,144 @@ EXPECT_EQ(result.error(), QuotaError::kNotFound); } -TEST_P(QuotaDatabaseTest, StorageKeyLastModifiedBetween) { +TEST_P(QuotaDatabaseTest, GetStorageKeysForType) { QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound)); - std::set<StorageKey> storage_keys; - EXPECT_TRUE(db.GetStorageKeysModifiedBetween( - kTemp, &storage_keys, base::Time(), base::Time::Max())); - EXPECT_TRUE(storage_keys.empty()); + const StorageKey storage_key1 = + StorageKey::CreateFromStringForTesting("http://example-a/"); + const StorageKey storage_key2 = + StorageKey::CreateFromStringForTesting("http://example-b/"); + const StorageKey storage_key3 = + StorageKey::CreateFromStringForTesting("http://example-c/"); - const StorageKey kStorageKey1 = - StorageKey::CreateFromStringForTesting("http://a/"); - const StorageKey kStorageKey2 = - StorageKey::CreateFromStringForTesting("http://b/"); - const StorageKey kStorageKey3 = - StorageKey::CreateFromStringForTesting("http://c/"); + QuotaErrorOr<BucketInfo> temp_bucket1 = + db.CreateBucketForTesting(storage_key1, "bucket_a", kTemp); + QuotaErrorOr<BucketInfo> temp_bucket2 = + db.CreateBucketForTesting(storage_key2, "bucket_b", kTemp); + QuotaErrorOr<BucketInfo> perm_bucket1 = + db.CreateBucketForTesting(storage_key2, "bucket_b", kPerm); + QuotaErrorOr<BucketInfo> perm_bucket2 = + db.CreateBucketForTesting(storage_key3, "bucket_b", kPerm); - // Report last mod time for the test storage_keys. - EXPECT_TRUE(db.SetStorageKeyLastModifiedTime(kStorageKey1, kTemp, - base::Time::FromJavaTime(0))); - EXPECT_TRUE(db.SetStorageKeyLastModifiedTime(kStorageKey2, kTemp, - base::Time::FromJavaTime(10))); - EXPECT_TRUE(db.SetStorageKeyLastModifiedTime(kStorageKey3, kTemp, - base::Time::FromJavaTime(20))); + QuotaErrorOr<std::set<StorageKey>> result = db.GetStorageKeysForType(kTemp); + ASSERT_TRUE(result.ok()); + ASSERT_TRUE(base::Contains(result.value(), storage_key1)); + ASSERT_TRUE(base::Contains(result.value(), storage_key2)); + ASSERT_FALSE(base::Contains(result.value(), storage_key3)); - EXPECT_TRUE(db.GetStorageKeysModifiedBetween( - kTemp, &storage_keys, base::Time(), base::Time::Max())); - EXPECT_EQ(3U, storage_keys.size()); - EXPECT_EQ(1U, storage_keys.count(kStorageKey1)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey2)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey3)); - - EXPECT_TRUE(db.GetStorageKeysModifiedBetween( - kTemp, &storage_keys, base::Time::FromJavaTime(5), base::Time::Max())); - EXPECT_EQ(2U, storage_keys.size()); - EXPECT_EQ(0U, storage_keys.count(kStorageKey1)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey2)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey3)); - - EXPECT_TRUE(db.GetStorageKeysModifiedBetween( - kTemp, &storage_keys, base::Time::FromJavaTime(15), base::Time::Max())); - EXPECT_EQ(1U, storage_keys.size()); - EXPECT_EQ(0U, storage_keys.count(kStorageKey1)); - EXPECT_EQ(0U, storage_keys.count(kStorageKey2)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey3)); - - EXPECT_TRUE(db.GetStorageKeysModifiedBetween( - kTemp, &storage_keys, base::Time::FromJavaTime(25), base::Time::Max())); - EXPECT_TRUE(storage_keys.empty()); - - EXPECT_TRUE(db.GetStorageKeysModifiedBetween(kTemp, &storage_keys, - base::Time::FromJavaTime(5), - base::Time::FromJavaTime(15))); - EXPECT_EQ(1U, storage_keys.size()); - EXPECT_EQ(0U, storage_keys.count(kStorageKey1)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey2)); - EXPECT_EQ(0U, storage_keys.count(kStorageKey3)); - - EXPECT_TRUE(db.GetStorageKeysModifiedBetween(kTemp, &storage_keys, - base::Time::FromJavaTime(0), - base::Time::FromJavaTime(20))); - EXPECT_EQ(2U, storage_keys.size()); - EXPECT_EQ(1U, storage_keys.count(kStorageKey1)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey2)); - EXPECT_EQ(0U, storage_keys.count(kStorageKey3)); - - // Update storage key's mod time but for persistent storage. - EXPECT_TRUE(db.SetStorageKeyLastModifiedTime(kStorageKey1, kPerm, - base::Time::FromJavaTime(30))); - - // Must have no effects on temporary storage_keys info. - EXPECT_TRUE(db.GetStorageKeysModifiedBetween( - kTemp, &storage_keys, base::Time::FromJavaTime(5), base::Time::Max())); - EXPECT_EQ(2U, storage_keys.size()); - EXPECT_EQ(0U, storage_keys.count(kStorageKey1)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey2)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey3)); - - // One more update for persistent storage key. - EXPECT_TRUE(db.SetStorageKeyLastModifiedTime(kStorageKey2, kPerm, - base::Time::FromJavaTime(40))); - - EXPECT_TRUE(db.GetStorageKeysModifiedBetween( - kPerm, &storage_keys, base::Time::FromJavaTime(25), base::Time::Max())); - EXPECT_EQ(2U, storage_keys.size()); - EXPECT_EQ(1U, storage_keys.count(kStorageKey1)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey2)); - EXPECT_EQ(0U, storage_keys.count(kStorageKey3)); - - EXPECT_TRUE(db.GetStorageKeysModifiedBetween( - kPerm, &storage_keys, base::Time::FromJavaTime(35), base::Time::Max())); - EXPECT_EQ(1U, storage_keys.size()); - EXPECT_EQ(0U, storage_keys.count(kStorageKey1)); - EXPECT_EQ(1U, storage_keys.count(kStorageKey2)); - EXPECT_EQ(0U, storage_keys.count(kStorageKey3)); + result = db.GetStorageKeysForType(kPerm); + ASSERT_TRUE(result.ok()); + ASSERT_FALSE(base::Contains(result.value(), storage_key1)); + ASSERT_TRUE(base::Contains(result.value(), storage_key2)); + ASSERT_TRUE(base::Contains(result.value(), storage_key3)); } TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) { QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound)); - std::set<BucketId> bucket_ids; - EXPECT_TRUE(db.GetBucketsModifiedBetween(kTemp, &bucket_ids, base::Time(), - base::Time::Max())); - EXPECT_TRUE(bucket_ids.empty()); + QuotaErrorOr<std::set<BucketInfo>> result = + db.GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max()); + EXPECT_TRUE(result.ok()); + std::set<BucketInfo> buckets = result.value(); + EXPECT_TRUE(buckets.empty()); - // Insert bucket entries into BucketTable. - base::Time now = base::Time::Now(); - using Entry = QuotaDatabase::BucketTableEntry; - Entry bucket1 = Entry( - BucketId(1), StorageKey::CreateFromStringForTesting("http://example-a/"), - kTemp, "bucket_a", 0, now, now); - Entry bucket2 = Entry( - BucketId(2), StorageKey::CreateFromStringForTesting("http://example-b/"), - kTemp, "bucket_b", 0, now, now); - Entry bucket3 = Entry( - BucketId(3), StorageKey::CreateFromStringForTesting("http://example-c/"), - kTemp, "bucket_c", 0, now, now); - Entry bucket4 = Entry( - BucketId(4), StorageKey::CreateFromStringForTesting("http://example-d/"), - kPerm, "bucket_d", 0, now, now); - Entry kTableEntries[] = {bucket1, bucket2, bucket3, bucket4}; - AssignBucketTable(&db, kTableEntries); + QuotaErrorOr<BucketInfo> result1 = db.CreateBucketForTesting( + StorageKey::CreateFromStringForTesting("http://example-a/"), "bucket_a", + kTemp); + EXPECT_TRUE(result1.ok()); + BucketInfo bucket1 = result1.value(); + QuotaErrorOr<BucketInfo> result2 = db.CreateBucketForTesting( + StorageKey::CreateFromStringForTesting("http://example-b/"), "bucket_b", + kTemp); + EXPECT_TRUE(result2.ok()); + BucketInfo bucket2 = result2.value(); + QuotaErrorOr<BucketInfo> result3 = db.CreateBucketForTesting( + StorageKey::CreateFromStringForTesting("http://example-c/"), "bucket_c", + kTemp); + EXPECT_TRUE(result3.ok()); + BucketInfo bucket3 = result3.value(); + QuotaErrorOr<BucketInfo> result4 = db.CreateBucketForTesting( + StorageKey::CreateFromStringForTesting("http://example-d/"), "bucket_d", + kPerm); + EXPECT_TRUE(result4.ok()); + BucketInfo bucket4 = result4.value(); - // Report last mod time for the buckets. - EXPECT_TRUE(db.SetBucketLastModifiedTime(bucket1.bucket_id, - base::Time::FromJavaTime(0))); - EXPECT_TRUE(db.SetBucketLastModifiedTime(bucket2.bucket_id, - base::Time::FromJavaTime(10))); - EXPECT_TRUE(db.SetBucketLastModifiedTime(bucket3.bucket_id, - base::Time::FromJavaTime(20))); - EXPECT_TRUE(db.SetBucketLastModifiedTime(bucket4.bucket_id, - base::Time::FromJavaTime(30))); + // Report last modified time for the buckets. + EXPECT_TRUE( + db.SetBucketLastModifiedTime(bucket1.id, base::Time::FromJavaTime(0))); + EXPECT_TRUE( + db.SetBucketLastModifiedTime(bucket2.id, base::Time::FromJavaTime(10))); + EXPECT_TRUE( + db.SetBucketLastModifiedTime(bucket3.id, base::Time::FromJavaTime(20))); + EXPECT_TRUE( + db.SetBucketLastModifiedTime(bucket4.id, base::Time::FromJavaTime(30))); - EXPECT_TRUE(db.GetBucketsModifiedBetween(kTemp, &bucket_ids, base::Time(), - base::Time::Max())); - EXPECT_EQ(3U, bucket_ids.size()); - EXPECT_EQ(1U, bucket_ids.count(bucket1.bucket_id)); - EXPECT_EQ(1U, bucket_ids.count(bucket2.bucket_id)); - EXPECT_EQ(1U, bucket_ids.count(bucket3.bucket_id)); - EXPECT_EQ(0U, bucket_ids.count(bucket4.bucket_id)); + result = db.GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max()); + EXPECT_TRUE(result.ok()); + buckets = result.value(); + EXPECT_EQ(3U, buckets.size()); + EXPECT_EQ(1U, buckets.count(bucket1)); + EXPECT_EQ(1U, buckets.count(bucket2)); + EXPECT_EQ(1U, buckets.count(bucket3)); + EXPECT_EQ(0U, buckets.count(bucket4)); - EXPECT_TRUE(db.GetBucketsModifiedBetween( - kTemp, &bucket_ids, base::Time::FromJavaTime(5), base::Time::Max())); - EXPECT_EQ(2U, bucket_ids.size()); - EXPECT_EQ(0U, bucket_ids.count(bucket1.bucket_id)); - EXPECT_EQ(1U, bucket_ids.count(bucket2.bucket_id)); - EXPECT_EQ(1U, bucket_ids.count(bucket3.bucket_id)); - EXPECT_EQ(0U, bucket_ids.count(bucket4.bucket_id)); + result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5), + base::Time::Max()); + EXPECT_TRUE(result.ok()); + buckets = result.value(); + EXPECT_EQ(2U, buckets.size()); + EXPECT_EQ(0U, buckets.count(bucket1)); + EXPECT_EQ(1U, buckets.count(bucket2)); + EXPECT_EQ(1U, buckets.count(bucket3)); + EXPECT_EQ(0U, buckets.count(bucket4)); - EXPECT_TRUE(db.GetBucketsModifiedBetween( - kTemp, &bucket_ids, base::Time::FromJavaTime(15), base::Time::Max())); - EXPECT_EQ(1U, bucket_ids.size()); - EXPECT_EQ(0U, bucket_ids.count(bucket1.bucket_id)); - EXPECT_EQ(0U, bucket_ids.count(bucket2.bucket_id)); - EXPECT_EQ(1U, bucket_ids.count(bucket3.bucket_id)); - EXPECT_EQ(0U, bucket_ids.count(bucket4.bucket_id)); + result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(15), + base::Time::Max()); + EXPECT_TRUE(result.ok()); + buckets = result.value(); + EXPECT_EQ(1U, buckets.size()); + EXPECT_EQ(0U, buckets.count(bucket1)); + EXPECT_EQ(0U, buckets.count(bucket2)); + EXPECT_EQ(1U, buckets.count(bucket3)); + EXPECT_EQ(0U, buckets.count(bucket4)); - EXPECT_TRUE(db.GetBucketsModifiedBetween( - kTemp, &bucket_ids, base::Time::FromJavaTime(25), base::Time::Max())); - EXPECT_TRUE(bucket_ids.empty()); + result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(25), + base::Time::Max()); + EXPECT_TRUE(result.ok()); + buckets = result.value(); + EXPECT_TRUE(buckets.empty()); - EXPECT_TRUE(db.GetBucketsModifiedBetween(kTemp, &bucket_ids, - base::Time::FromJavaTime(5), - base::Time::FromJavaTime(15))); - EXPECT_EQ(1U, bucket_ids.size()); - EXPECT_EQ(0U, bucket_ids.count(bucket1.bucket_id)); - EXPECT_EQ(1U, bucket_ids.count(bucket2.bucket_id)); - EXPECT_EQ(0U, bucket_ids.count(bucket3.bucket_id)); - EXPECT_EQ(0U, bucket_ids.count(bucket4.bucket_id)); + result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5), + base::Time::FromJavaTime(15)); + EXPECT_TRUE(result.ok()); + buckets = result.value(); + EXPECT_EQ(1U, buckets.size()); + EXPECT_EQ(0U, buckets.count(bucket1)); + EXPECT_EQ(1U, buckets.count(bucket2)); + EXPECT_EQ(0U, buckets.count(bucket3)); + EXPECT_EQ(0U, buckets.count(bucket4)); - EXPECT_TRUE(db.GetBucketsModifiedBetween(kTemp, &bucket_ids, - base::Time::FromJavaTime(0), - base::Time::FromJavaTime(20))); - EXPECT_EQ(2U, bucket_ids.size()); - EXPECT_EQ(1U, bucket_ids.count(bucket1.bucket_id)); - EXPECT_EQ(1U, bucket_ids.count(bucket2.bucket_id)); - EXPECT_EQ(0U, bucket_ids.count(bucket3.bucket_id)); - EXPECT_EQ(0U, bucket_ids.count(bucket4.bucket_id)); + result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(0), + base::Time::FromJavaTime(20)); + EXPECT_TRUE(result.ok()); + buckets = result.value(); + EXPECT_EQ(2U, buckets.size()); + EXPECT_EQ(1U, buckets.count(bucket1)); + EXPECT_EQ(1U, buckets.count(bucket2)); + EXPECT_EQ(0U, buckets.count(bucket3)); + EXPECT_EQ(0U, buckets.count(bucket4)); - EXPECT_TRUE(db.GetBucketsModifiedBetween(kPerm, &bucket_ids, - base::Time::FromJavaTime(0), - base::Time::FromJavaTime(35))); - EXPECT_EQ(1U, bucket_ids.size()); - EXPECT_EQ(0U, bucket_ids.count(bucket1.bucket_id)); - EXPECT_EQ(0U, bucket_ids.count(bucket2.bucket_id)); - EXPECT_EQ(0U, bucket_ids.count(bucket3.bucket_id)); - EXPECT_EQ(1U, bucket_ids.count(bucket4.bucket_id)); + result = db.GetBucketsModifiedBetween(kPerm, base::Time::FromJavaTime(0), + base::Time::FromJavaTime(35)); + EXPECT_TRUE(result.ok()); + buckets = result.value(); + EXPECT_EQ(1U, buckets.size()); + EXPECT_EQ(0U, buckets.count(bucket1)); + EXPECT_EQ(0U, buckets.count(bucket2)); + EXPECT_EQ(0U, buckets.count(bucket3)); + EXPECT_EQ(1U, buckets.count(bucket4)); } TEST_P(QuotaDatabaseTest, RegisterInitialStorageKeyInfo) {
diff --git a/storage/browser/quota/quota_manager_impl.cc b/storage/browser/quota/quota_manager_impl.cc index 7285da4a..700e2d87 100644 --- a/storage/browser/quota/quota_manager_impl.cc +++ b/storage/browser/quota/quota_manager_impl.cc
@@ -120,6 +120,22 @@ return database->GetBucket(storage_key, bucket_name, type); } +QuotaErrorOr<std::set<StorageKey>> GetStorageKeysForTypeOnDBThread( + StorageType type, + QuotaDatabase* database) { + DCHECK(database); + return database->GetStorageKeysForType(type); +} + +QuotaErrorOr<std::set<BucketInfo>> GetModifiedBetweenOnDBThread( + StorageType type, + base::Time begin, + base::Time end, + QuotaDatabase* database) { + DCHECK(database); + return database->GetBucketsModifiedBetween(type, begin, end); +} + bool GetPersistentHostQuotaOnDBThread(const std::string& host, int64_t* quota, QuotaDatabase* database) { @@ -917,42 +933,6 @@ base::WeakPtrFactory<StorageCleanupHelper> weak_factory_{this}; }; -// Fetch storage keys that have been modified since the specified time. This is -// used to clear data for storage keys that have been modified within the user -// specified time frame. -// -// This class is granted ownership of itself when it is passed to -// DidGetModifiedBetween() via base::Owned(). When the closure for said -// function goes out of scope, the object is deleted. This is a thread-safe -// class. -class QuotaManagerImpl::GetModifiedSinceHelper { - public: - bool GetModifiedBetweenOnDBThread(StorageType type, - base::Time begin, - base::Time end, - QuotaDatabase* database) { - DCHECK(database); - return database->GetStorageKeysModifiedBetween(type, &storage_keys_, begin, - end); - } - - void DidGetModifiedBetween(const base::WeakPtr<QuotaManagerImpl>& manager, - GetStorageKeysCallback callback, - StorageType type, - bool success) { - if (!manager) { - // The operation was aborted. - std::move(callback).Run(std::set<StorageKey>(), type); - return; - } - manager->DidDatabaseWork(success); - std::move(callback).Run(storage_keys_, type); - } - - private: - std::set<StorageKey> storage_keys_; -}; - // Gather storage key info table for quota-internals page. // // This class is granted ownership of itself when it is passed to @@ -1107,6 +1087,17 @@ weak_factory_.GetWeakPtr(), std::move(callback))); } +void QuotaManagerImpl::GetStorageKeysForType(blink::mojom::StorageType type, + GetStorageKeysCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + LazyInitialize(); + + PostTaskAndReplyWithResultForDBThread( + base::BindOnce(&GetStorageKeysForTypeOnDBThread, type), + base::BindOnce(&QuotaManagerImpl::DidGetStorageKeys, + weak_factory_.GetWeakPtr(), std::move(callback))); +} + void QuotaManagerImpl::GetUsageInfo(GetUsageInfoCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); LazyInitialize(); @@ -1258,6 +1249,14 @@ std::move(callback)); } +void QuotaManagerImpl::DeleteBucketData(const BucketInfo& bucket, + QuotaClientTypes quota_client_types, + StatusCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DeleteBucketDataInternal(bucket, std::move(quota_client_types), false, + std::move(callback)); +} + void QuotaManagerImpl::PerformStorageCleanup( StorageType type, QuotaClientTypes quota_client_types, @@ -1393,21 +1392,16 @@ storage_key.origin().GetURL()); } -void QuotaManagerImpl::GetStorageKeysModifiedBetween( - StorageType type, - base::Time begin, - base::Time end, - GetStorageKeysCallback callback) { +void QuotaManagerImpl::GetBucketsModifiedBetween(StorageType type, + base::Time begin, + base::Time end, + GetBucketsCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); LazyInitialize(); - GetModifiedSinceHelper* helper = new GetModifiedSinceHelper; PostTaskAndReplyWithResultForDBThread( - FROM_HERE, - base::BindOnce(&GetModifiedSinceHelper::GetModifiedBetweenOnDBThread, - base::Unretained(helper), type, begin, end), - base::BindOnce(&GetModifiedSinceHelper::DidGetModifiedBetween, - base::Owned(helper), weak_factory_.GetWeakPtr(), - std::move(callback), type)); + base::BindOnce(&GetModifiedBetweenOnDBThread, type, begin, end), + base::BindOnce(&QuotaManagerImpl::DidGetModifiedBetween, + weak_factory_.GetWeakPtr(), std::move(callback), type)); } bool QuotaManagerImpl::ResetUsageTracker(StorageType type) { @@ -2138,6 +2132,23 @@ std::move(callback).Run(std::move(result)); } +void QuotaManagerImpl::DidGetStorageKeys( + GetStorageKeysCallback callback, + QuotaErrorOr<std::set<StorageKey>> result) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DidDatabaseWork(result.ok() || result.error() != QuotaError::kDatabaseError); + std::move(callback).Run(std::move(result.value())); +} + +void QuotaManagerImpl::DidGetModifiedBetween( + GetBucketsCallback callback, + StorageType type, + QuotaErrorOr<std::set<BucketInfo>> result) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DidDatabaseWork(result.ok() || result.error() != QuotaError::kDatabaseError); + std::move(callback).Run(result.value(), type); +} + void QuotaManagerImpl::PostTaskAndReplyWithResultForDBThread( const base::Location& from_here, base::OnceCallback<bool(QuotaDatabase*)> task,
diff --git a/storage/browser/quota/quota_manager_impl.h b/storage/browser/quota/quota_manager_impl.h index c44fa64..028a20e 100644 --- a/storage/browser/quota/quota_manager_impl.h +++ b/storage/browser/quota/quota_manager_impl.h
@@ -187,6 +187,11 @@ blink::mojom::StorageType type, base::OnceCallback<void(QuotaErrorOr<BucketInfo>)>); + // Retrieves all storage keys for `type` that are in the bucket database. + // Used for listing storage keys when showing storage key quota usage. + void GetStorageKeysForType(blink::mojom::StorageType type, + GetStorageKeysCallback callback); + // Called by clients or webapps. Returns usage per host. void GetUsageInfo(GetUsageInfoCallback callback); @@ -265,14 +270,18 @@ // DeleteStorageKeyData and DeleteHostData (surprisingly enough) delete data // of a particular blink::mojom::StorageType associated with either a specific - // storage key or set of storage keys. Each method additionally requires a - // |quota_client_types| which specifies the types of QuotaClients to delete - // from the storage key. Pass in QuotaClientType::AllClients() to remove all - // clients from the storage key, regardless of type. + // storage key or set of storage keys. DeleteBucketData will delete only the + // specified bucket. Each method additionally requires a |quota_client_types| + // which specifies the types of QuotaClients to delete from the storage key. + // Pass in QuotaClientType::AllClients() to remove all clients from the + // storage key, regardless of type. virtual void DeleteStorageKeyData(const blink::StorageKey& storage_key, blink::mojom::StorageType type, QuotaClientTypes quota_client_types, StatusCallback callback); + virtual void DeleteBucketData(const BucketInfo& bucket, + QuotaClientTypes quota_client_types, + StatusCallback callback); void DeleteHostData(const std::string& host, blink::mojom::StorageType type, QuotaClientTypes quota_client_types, @@ -300,10 +309,10 @@ bool IsStorageUnlimited(const blink::StorageKey& storage_key, blink::mojom::StorageType type) const; - virtual void GetStorageKeysModifiedBetween(blink::mojom::StorageType type, - base::Time begin, - base::Time end, - GetStorageKeysCallback callback); + virtual void GetBucketsModifiedBetween(blink::mojom::StorageType type, + base::Time begin, + base::Time end, + GetBucketsCallback callback); bool ResetUsageTracker(blink::mojom::StorageType type); @@ -374,7 +383,6 @@ class BucketDataDeleter; class StorageKeyDataDeleter; class HostDataDeleter; - class GetModifiedSinceHelper; class DumpQuotaTableHelper; class DumpBucketTableHelper; class StorageCleanupHelper; @@ -451,6 +459,9 @@ bool is_eviction, StatusCallback callback); + // Runs StorageKeyDataDeleter which calls QuotaClients to clear all data for + // the storage key. Once the task is complete, calls the QuotaDatabase to + // delete buckets for the storage key & storage type from the bucket table. void DeleteStorageKeyDataInternal(const blink::StorageKey& storage_key, blink::mojom::StorageType type, QuotaClientTypes quota_client_types, @@ -512,6 +523,11 @@ void DidGetBucket(base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback, QuotaErrorOr<BucketInfo> result); + void DidGetStorageKeys(GetStorageKeysCallback callback, + QuotaErrorOr<std::set<blink::StorageKey>> result); + void DidGetModifiedBetween(GetBucketsCallback callback, + blink::mojom::StorageType type, + QuotaErrorOr<std::set<BucketInfo>> result); void DeleteOnCorrectThread() const;
diff --git a/storage/browser/quota/quota_manager_unittest.cc b/storage/browser/quota/quota_manager_unittest.cc index 86f901b5..bffd8b3 100644 --- a/storage/browser/quota/quota_manager_unittest.cc +++ b/storage/browser/quota/quota_manager_unittest.cc
@@ -171,6 +171,15 @@ run_loop.Run(); } + void GetStorageKeysForType(blink::mojom::StorageType storage_type) { + base::RunLoop run_loop; + quota_manager_impl_->GetStorageKeysForType( + storage_type, + base::BindOnce(&QuotaManagerImplTest::DidGetStorageKeys, + weak_factory_.GetWeakPtr(), run_loop.QuitClosure())); + run_loop.Run(); + } + void GetUsageInfo() { usage_info_.clear(); quota_manager_impl_->GetUsageInfo(base::BindOnce( @@ -300,6 +309,15 @@ weak_factory_.GetWeakPtr())); } + void DeleteBucketData(const BucketInfo& bucket, + QuotaClientTypes quota_client_types) { + quota_status_ = QuotaStatusCode::kUnknown; + quota_manager_impl_->DeleteBucketData( + bucket, std::move(quota_client_types), + base::BindOnce(&QuotaManagerImplTest::StatusCallback, + weak_factory_.GetWeakPtr())); + } + void DeleteHostData(const std::string& host, StorageType type, QuotaClientTypes quota_client_types) { @@ -360,15 +378,17 @@ quota_manager_impl_->NotifyStorageKeyNoLongerInUse(storage_key); } - void GetStorageKeysModifiedBetween(StorageType type, - base::Time begin, - base::Time end) { - modified_storage_keys_.clear(); - modified_storage_keys_type_ = StorageType::kUnknown; - quota_manager_impl_->GetStorageKeysModifiedBetween( + void GetBucketsModifiedBetween(StorageType type, + base::Time begin, + base::Time end) { + modified_buckets_.clear(); + modified_buckets_type_ = StorageType::kUnknown; + base::RunLoop run_loop; + quota_manager_impl_->GetBucketsModifiedBetween( type, begin, end, - base::BindOnce(&QuotaManagerImplTest::DidGetModifiedStorageKeys, - weak_factory_.GetWeakPtr())); + base::BindOnce(&QuotaManagerImplTest::DidGetModifiedBuckets, + weak_factory_.GetWeakPtr(), run_loop.QuitClosure())); + run_loop.Run(); } void DumpQuotaTable() { @@ -389,6 +409,12 @@ std::move(quit_closure).Run(); } + void DidGetStorageKeys(base::OnceClosure quit_closure, + const std::set<StorageKey>& storage_keys) { + storage_keys_ = std::move(storage_keys); + std::move(quit_closure).Run(); + } + void DidGetUsageInfo(UsageInfoEntries entries) { usage_info_ = std::move(entries); } @@ -465,10 +491,12 @@ !bucket->storage_key.origin().GetURL().is_empty()); } - void DidGetModifiedStorageKeys(const std::set<StorageKey>& storage_keys, - StorageType type) { - modified_storage_keys_ = storage_keys; - modified_storage_keys_type_ = type; + void DidGetModifiedBuckets(base::OnceClosure quit_closure, + const std::set<BucketInfo>& buckets, + StorageType type) { + modified_buckets_ = buckets; + modified_buckets_type_ = type; + std::move(quit_closure).Run(); } void DidDumpQuotaTable(const QuotaTableEntries& entries) { @@ -535,12 +563,10 @@ const absl::optional<BucketInfo>& eviction_bucket() const { return eviction_bucket_; } - const std::set<StorageKey>& modified_storage_keys() const { - return modified_storage_keys_; + const std::set<BucketInfo>& modified_buckets() const { + return modified_buckets_; } - StorageType modified_storage_keys_type() const { - return modified_storage_keys_type_; - } + StorageType modified_buckets_type() const { return modified_buckets_type_; } const QuotaTableEntries& quota_entries() const { return quota_entries_; } const BucketTableEntries& bucket_entries() const { return bucket_entries_; } const QuotaSettings& settings() const { return settings_; } @@ -551,6 +577,7 @@ base::test::ScopedFeatureList scoped_feature_list_; base::test::TaskEnvironment task_environment_; QuotaErrorOr<BucketInfo> bucket_; + QuotaErrorOr<std::set<StorageKey>> storage_keys_; static std::vector<QuotaClientType> AllClients() { // TODO(pwnall): Implement using something other than an empty vector? @@ -577,8 +604,8 @@ int64_t total_space_; int64_t available_space_; absl::optional<BucketInfo> eviction_bucket_; - std::set<StorageKey> modified_storage_keys_; - StorageType modified_storage_keys_type_; + std::set<BucketInfo> modified_buckets_; + StorageType modified_buckets_type_; QuotaTableEntries quota_entries_; BucketTableEntries bucket_entries_; QuotaSettings settings_; @@ -666,6 +693,36 @@ ASSERT_FALSE(is_db_disabled()); } +TEST_F(QuotaManagerImplTest, GetStorageKeysForType) { + StorageKey storage_key_a = ToStorageKey("http://a.com/"); + StorageKey storage_key_b = ToStorageKey("http://b.com/"); + StorageKey storage_key_c = ToStorageKey("http://c.com/"); + + CreateBucketForTesting(storage_key_a, "bucket_a", kTemp); + EXPECT_TRUE(bucket_.ok()); + BucketInfo bucket_a = bucket_.value(); + + CreateBucketForTesting(storage_key_b, "bucket_b", kTemp); + EXPECT_TRUE(bucket_.ok()); + BucketInfo bucket_b = bucket_.value(); + + CreateBucketForTesting(storage_key_c, "bucket_c", kPerm); + EXPECT_TRUE(bucket_.ok()); + BucketInfo bucket_c = bucket_.value(); + + GetStorageKeysForType(kTemp); + EXPECT_EQ(2U, storage_keys_->size()); + ASSERT_EQ(1U, storage_keys_->count(storage_key_a)); + ASSERT_EQ(1U, storage_keys_->count(storage_key_b)); + ASSERT_EQ(0U, storage_keys_->count(storage_key_c)); + + GetStorageKeysForType(kPerm); + EXPECT_EQ(1U, storage_keys_->size()); + ASSERT_EQ(0U, storage_keys_->count(storage_key_a)); + ASSERT_EQ(0U, storage_keys_->count(storage_key_b)); + ASSERT_EQ(1U, storage_keys_->count(storage_key_c)); +} + TEST_F(QuotaManagerImplTest, GetUsageAndQuota_Simple) { static const MockStorageKeyData kData[] = { {"http://foo.com/", kTemp, 10}, @@ -2335,6 +2392,225 @@ EXPECT_EQ(predelete_bar_pers - 1000, usage()); } +TEST_F(QuotaManagerImplTest, DeleteBucketNoClients) { + CreateBucketForTesting(ToStorageKey("http://foo.com"), kDefaultBucketName, + kTemp); + ASSERT_TRUE(bucket_.ok()); + + DeleteBucketData(bucket_.value(), AllQuotaClientTypes()); + task_environment_.RunUntilIdle(); + EXPECT_EQ(QuotaStatusCode::kOk, status()); +} + +TEST_F(QuotaManagerImplTest, DeleteBucketDataMultiple) { + static const MockStorageKeyData kData1[] = { + {"http://foo.com/", kTemp, 1}, + {"http://foo.com:1/", kTemp, 20}, + {"http://foo.com/", kPerm, 300}, + {"http://bar.com/", kTemp, 4000}, + }; + static const MockStorageKeyData kData2[] = { + {"http://foo.com/", kTemp, 50000}, {"http://foo.com:1/", kTemp, 6000}, + {"http://foo.com/", kPerm, 700}, {"https://foo.com/", kTemp, 80}, + {"http://bar.com/", kTemp, 9}, + }; + CreateAndRegisterClient(kData1, QuotaClientType::kFileSystem, + {blink::mojom::StorageType::kTemporary, + blink::mojom::StorageType::kPersistent}); + CreateAndRegisterClient(kData2, QuotaClientType::kDatabase, + {blink::mojom::StorageType::kTemporary, + blink::mojom::StorageType::kPersistent}); + + CreateBucketForTesting(ToStorageKey("http://foo.com"), kDefaultBucketName, + kTemp); + ASSERT_TRUE(bucket_.ok()); + BucketInfo foo_temp_bucket = bucket_.value(); + + CreateBucketForTesting(ToStorageKey("http://bar.com"), kDefaultBucketName, + kTemp); + ASSERT_TRUE(bucket_.ok()); + BucketInfo bar_temp_bucket = bucket_.value(); + + GetGlobalUsage(kTemp); + task_environment_.RunUntilIdle(); + const int64_t predelete_global_tmp = usage(); + + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + const int64_t predelete_foo_tmp = usage(); + + GetHostUsageWithBreakdown("bar.com", kTemp); + task_environment_.RunUntilIdle(); + const int64_t predelete_bar_tmp = usage(); + + GetHostUsageWithBreakdown("foo.com", kPerm); + task_environment_.RunUntilIdle(); + const int64_t predelete_foo_pers = usage(); + + GetHostUsageWithBreakdown("bar.com", kPerm); + task_environment_.RunUntilIdle(); + const int64_t predelete_bar_pers = usage(); + + for (const MockStorageKeyData& data : kData1) { + quota_manager_impl()->NotifyStorageAccessed(ToStorageKey(data.origin), + data.type, base::Time::Now()); + } + for (const MockStorageKeyData& data : kData2) { + quota_manager_impl()->NotifyStorageAccessed(ToStorageKey(data.origin), + data.type, base::Time::Now()); + } + task_environment_.RunUntilIdle(); + + reset_status_callback_count(); + DeleteBucketData(foo_temp_bucket, AllQuotaClientTypes()); + DeleteBucketData(bar_temp_bucket, AllQuotaClientTypes()); + DeleteBucketData(foo_temp_bucket, AllQuotaClientTypes()); + task_environment_.RunUntilIdle(); + + EXPECT_EQ(3, status_callback_count()); + + DumpBucketTable(); + task_environment_.RunUntilIdle(); + + for (const auto& entry : bucket_entries()) { + if (entry.type != kTemp) + continue; + + EXPECT_NE(std::string("http://foo.com/"), + entry.storage_key.origin().GetURL().spec()); + EXPECT_NE(std::string("http://bar.com/"), + entry.storage_key.origin().GetURL().spec()); + } + + GetGlobalUsage(kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_global_tmp - (1 + 4000 + 50000 + 9), usage()); + + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - (1 + 50000), usage()); + + GetHostUsageWithBreakdown("bar.com", kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_bar_tmp - (4000 + 9), usage()); + + GetHostUsageWithBreakdown("foo.com", kPerm); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_foo_pers, usage()); + + GetHostUsageWithBreakdown("bar.com", kPerm); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_bar_pers, usage()); +} + +TEST_F(QuotaManagerImplTest, DeleteBucketDataMultipleClientsDifferentTypes) { + static const MockStorageKeyData kData1[] = { + {"http://foo.com/", kPerm, 1}, + {"http://foo.com:1/", kPerm, 10}, + {"http://foo.com/", kTemp, 100}, + {"http://bar.com/", kPerm, 1000}, + }; + static const MockStorageKeyData kData2[] = { + {"http://foo.com/", kTemp, 10000}, + {"http://foo.com:1/", kTemp, 100000}, + {"https://foo.com/", kTemp, 1000000}, + {"http://bar.com/", kTemp, 10000000}, + }; + CreateAndRegisterClient(kData1, QuotaClientType::kFileSystem, + {blink::mojom::StorageType::kTemporary, + blink::mojom::StorageType::kPersistent}); + CreateAndRegisterClient(kData2, QuotaClientType::kDatabase, + {blink::mojom::StorageType::kTemporary}); + + CreateBucketForTesting(ToStorageKey("http://foo.com/"), kDefaultBucketName, + kPerm); + ASSERT_TRUE(bucket_.ok()); + BucketInfo foo_perm_bucket = bucket_.value(); + + CreateBucketForTesting(ToStorageKey("http://bar.com/"), kDefaultBucketName, + kPerm); + ASSERT_TRUE(bucket_.ok()); + BucketInfo bar_perm_bucket = bucket_.value(); + + GetGlobalUsage(kTemp); + task_environment_.RunUntilIdle(); + const int64_t predelete_global_tmp = usage(); + + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + const int64_t predelete_foo_tmp = usage(); + + GetHostUsageWithBreakdown("bar.com", kTemp); + task_environment_.RunUntilIdle(); + const int64_t predelete_bar_tmp = usage(); + + GetGlobalUsage(kPerm); + task_environment_.RunUntilIdle(); + const int64_t predelete_global_pers = usage(); + + GetHostUsageWithBreakdown("foo.com", kPerm); + task_environment_.RunUntilIdle(); + const int64_t predelete_foo_pers = usage(); + + GetHostUsageWithBreakdown("bar.com", kPerm); + task_environment_.RunUntilIdle(); + const int64_t predelete_bar_pers = usage(); + + for (const MockStorageKeyData& data : kData1) { + quota_manager_impl()->NotifyStorageAccessed(ToStorageKey(data.origin), + data.type, base::Time::Now()); + } + for (const MockStorageKeyData& data : kData2) { + quota_manager_impl()->NotifyStorageAccessed(ToStorageKey(data.origin), + data.type, base::Time::Now()); + } + task_environment_.RunUntilIdle(); + + reset_status_callback_count(); + DeleteBucketData(foo_perm_bucket, AllQuotaClientTypes()); + DeleteBucketData(bar_perm_bucket, AllQuotaClientTypes()); + task_environment_.RunUntilIdle(); + + EXPECT_EQ(2, status_callback_count()); + + DumpBucketTable(); + task_environment_.RunUntilIdle(); + + for (const auto& entry : bucket_entries()) { + if (entry.type != kPerm) + continue; + + EXPECT_NE(std::string("http://foo.com/"), + entry.storage_key.origin().GetURL().spec()); + EXPECT_NE(std::string("http://bar.com/"), + entry.storage_key.origin().GetURL().spec()); + } + + GetGlobalUsage(kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_global_tmp, usage()); + + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp, usage()); + + GetHostUsageWithBreakdown("bar.com", kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_bar_tmp, usage()); + + GetGlobalUsage(kPerm); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_global_pers - (1 + 1000), usage()); + + GetHostUsageWithBreakdown("foo.com", kPerm); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_foo_pers - 1, usage()); + + GetHostUsageWithBreakdown("bar.com", kPerm); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_bar_pers - 1000, usage()); +} + TEST_F(QuotaManagerImplTest, GetCachedStorageKeys) { static const MockStorageKeyData kData[] = { {"http://a.com/", kTemp, 1}, @@ -2474,7 +2750,7 @@ EXPECT_EQ(ToStorageKey("http://a.com/"), eviction_bucket()->storage_key); } -TEST_F(QuotaManagerImplTest, GetStorageKeysModifiedBetween) { +TEST_F(QuotaManagerImplTest, GetBucketsModifiedBetween) { static const MockStorageKeyData kData[] = { {"http://a.com/", kTemp, 0}, {"http://a.com:1/", kTemp, 0}, {"https://a.com/", kTemp, 0}, {"http://b.com/", kPerm, 0}, // persistent @@ -2485,10 +2761,10 @@ {blink::mojom::StorageType::kTemporary, blink::mojom::StorageType::kPersistent}); - GetStorageKeysModifiedBetween(kTemp, base::Time(), base::Time::Max()); + GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max()); task_environment_.RunUntilIdle(); - EXPECT_TRUE(modified_storage_keys().empty()); - EXPECT_EQ(modified_storage_keys_type(), kTemp); + EXPECT_TRUE(modified_buckets().empty()); + EXPECT_EQ(modified_buckets_type(), kTemp); base::Time time1 = client->IncrementMockTime(); client->ModifyStorageKeyAndNotify(ToStorageKey("http://a.com/"), kTemp, 10); @@ -2499,31 +2775,38 @@ client->ModifyStorageKeyAndNotify(ToStorageKey("http://c.com/"), kTemp, 10); base::Time time3 = client->IncrementMockTime(); - GetStorageKeysModifiedBetween(kTemp, time1, base::Time::Max()); + GetBucketsModifiedBetween(kTemp, time1, base::Time::Max()); task_environment_.RunUntilIdle(); - EXPECT_EQ(4U, modified_storage_keys().size()); - EXPECT_EQ(modified_storage_keys_type(), kTemp); + EXPECT_EQ(4U, modified_buckets().size()); + EXPECT_EQ(modified_buckets_type(), kTemp); for (const MockStorageKeyData& data : kData) { - if (data.type == kTemp) - EXPECT_EQ(1U, modified_storage_keys().count(ToStorageKey(data.origin))); + if (data.type == kTemp) { + auto it = + std::find_if(modified_buckets().begin(), modified_buckets().end(), + [data](const BucketInfo& bucket) { + return ToStorageKey(data.origin) == bucket.storage_key; + }); + EXPECT_NE(it, modified_buckets().end()); + } } - GetStorageKeysModifiedBetween(kTemp, time2, base::Time::Max()); + GetBucketsModifiedBetween(kTemp, time2, base::Time::Max()); task_environment_.RunUntilIdle(); - EXPECT_EQ(2U, modified_storage_keys().size()); + EXPECT_EQ(2U, modified_buckets().size()); - GetStorageKeysModifiedBetween(kTemp, time3, base::Time::Max()); + GetBucketsModifiedBetween(kTemp, time3, base::Time::Max()); task_environment_.RunUntilIdle(); - EXPECT_TRUE(modified_storage_keys().empty()); - EXPECT_EQ(modified_storage_keys_type(), kTemp); + EXPECT_TRUE(modified_buckets().empty()); + EXPECT_EQ(modified_buckets_type(), kTemp); client->ModifyStorageKeyAndNotify(ToStorageKey("http://a.com/"), kTemp, 10); - GetStorageKeysModifiedBetween(kTemp, time3, base::Time::Max()); + GetBucketsModifiedBetween(kTemp, time3, base::Time::Max()); task_environment_.RunUntilIdle(); - EXPECT_EQ(1U, modified_storage_keys().size()); - EXPECT_EQ(1U, modified_storage_keys().count(ToStorageKey("http://a.com/"))); - EXPECT_EQ(modified_storage_keys_type(), kTemp); + EXPECT_EQ(1U, modified_buckets().size()); + EXPECT_EQ(modified_buckets().begin()->storage_key, + ToStorageKey("http://a.com/")); + EXPECT_EQ(modified_buckets_type(), kTemp); } TEST_F(QuotaManagerImplTest, DumpQuotaTable) { @@ -2650,6 +2933,62 @@ EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage()); } +TEST_F(QuotaManagerImplTest, DeleteSpecificClientTypeSingleBucket) { + static const MockStorageKeyData kData1[] = { + {"http://foo.com/", kTemp, 1}, + }; + static const MockStorageKeyData kData2[] = { + {"http://foo.com/", kTemp, 2}, + }; + static const MockStorageKeyData kData3[] = { + {"http://foo.com/", kTemp, 4}, + }; + static const MockStorageKeyData kData4[] = { + {"http://foo.com/", kTemp, 8}, + }; + CreateAndRegisterClient(kData1, QuotaClientType::kFileSystem, + {blink::mojom::StorageType::kTemporary}); + CreateAndRegisterClient(kData2, QuotaClientType::kAppcache, + {blink::mojom::StorageType::kTemporary}); + CreateAndRegisterClient(kData3, QuotaClientType::kDatabase, + {blink::mojom::StorageType::kTemporary}); + CreateAndRegisterClient(kData4, QuotaClientType::kIndexedDatabase, + {blink::mojom::StorageType::kTemporary}); + + CreateBucketForTesting(ToStorageKey("http://foo.com"), kDefaultBucketName, + kTemp); + ASSERT_TRUE(bucket_.ok()); + BucketInfo foo_bucket = bucket_.value(); + + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + const int64_t predelete_foo_tmp = usage(); + + DeleteBucketData(foo_bucket, {QuotaClientType::kFileSystem}); + task_environment_.RunUntilIdle(); + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 1, usage()); + + DeleteBucketData(foo_bucket, {QuotaClientType::kAppcache}); + task_environment_.RunUntilIdle(); + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage()); + + DeleteBucketData(foo_bucket, {QuotaClientType::kDatabase}); + task_environment_.RunUntilIdle(); + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 4 - 2 - 1, usage()); + + DeleteBucketData(foo_bucket, {QuotaClientType::kIndexedDatabase}); + task_environment_.RunUntilIdle(); + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage()); +} + TEST_F(QuotaManagerImplTest, DeleteSpecificClientTypeSingleHost) { static const MockStorageKeyData kData1[] = { {"http://foo.com:1111/", kTemp, 1}, @@ -2744,6 +3083,52 @@ EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage()); } +TEST_F(QuotaManagerImplTest, DeleteMultipleClientTypesSingleBucket) { + static const MockStorageKeyData kData1[] = { + {"http://foo.com/", kTemp, 1}, + }; + static const MockStorageKeyData kData2[] = { + {"http://foo.com/", kTemp, 2}, + }; + static const MockStorageKeyData kData3[] = { + {"http://foo.com/", kTemp, 4}, + }; + static const MockStorageKeyData kData4[] = { + {"http://foo.com/", kTemp, 8}, + }; + CreateAndRegisterClient(kData1, QuotaClientType::kFileSystem, + {blink::mojom::StorageType::kTemporary}); + CreateAndRegisterClient(kData2, QuotaClientType::kAppcache, + {blink::mojom::StorageType::kTemporary}); + CreateAndRegisterClient(kData3, QuotaClientType::kDatabase, + {blink::mojom::StorageType::kTemporary}); + CreateAndRegisterClient(kData4, QuotaClientType::kIndexedDatabase, + {blink::mojom::StorageType::kTemporary}); + + CreateBucketForTesting(ToStorageKey("http://foo.com/"), kDefaultBucketName, + kTemp); + ASSERT_TRUE(bucket_.ok()); + BucketInfo foo_bucket = bucket_.value(); + + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + const int64_t predelete_foo_tmp = usage(); + + DeleteBucketData(foo_bucket, + {QuotaClientType::kFileSystem, QuotaClientType::kDatabase}); + task_environment_.RunUntilIdle(); + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 4 - 1, usage()); + + DeleteBucketData(foo_bucket, {QuotaClientType::kAppcache, + QuotaClientType::kIndexedDatabase}); + task_environment_.RunUntilIdle(); + GetHostUsageWithBreakdown("foo.com", kTemp); + task_environment_.RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage()); +} + TEST_F(QuotaManagerImplTest, DeleteMultipleClientTypesSingleHost) { static const MockStorageKeyData kData1[] = { {"http://foo.com:1111/", kTemp, 1},
diff --git a/storage/browser/test/mock_quota_manager.cc b/storage/browser/test/mock_quota_manager.cc index 202702f..dcf7c01 100644 --- a/storage/browser/test/mock_quota_manager.cc +++ b/storage/browser/test/mock_quota_manager.cc
@@ -21,22 +21,19 @@ namespace storage { -MockQuotaManager::StorageKeyInfo::StorageKeyInfo( - const StorageKey& storage_key, - StorageType type, - QuotaClientTypes quota_client_types, - base::Time modified) - : storage_key(storage_key), - type(type), +MockQuotaManager::BucketData::BucketData(const BucketInfo& bucket, + QuotaClientTypes quota_client_types, + base::Time modified) + : bucket(bucket), quota_client_types(std::move(quota_client_types)), modified(modified) {} -MockQuotaManager::StorageKeyInfo::~StorageKeyInfo() = default; +MockQuotaManager::BucketData::~BucketData() = default; -MockQuotaManager::StorageKeyInfo::StorageKeyInfo( - MockQuotaManager::StorageKeyInfo&&) = default; -MockQuotaManager::StorageKeyInfo& MockQuotaManager::StorageKeyInfo::operator=( - MockQuotaManager::StorageKeyInfo&&) = default; +MockQuotaManager::BucketData::BucketData(MockQuotaManager::BucketData&&) = + default; +MockQuotaManager::BucketData& MockQuotaManager::BucketData::operator=( + MockQuotaManager::BucketData&&) = default; MockQuotaManager::StorageInfo::StorageInfo() : usage(0), quota(std::numeric_limits<int64_t>::max()) {} @@ -68,55 +65,74 @@ usage_and_quota_map_[std::make_pair(storage_key, type)].quota = quota; } -bool MockQuotaManager::AddStorageKey(const StorageKey& storage_key, - StorageType type, - QuotaClientTypes quota_client_types, - base::Time modified) { - storage_keys_.emplace_back(StorageKeyInfo( - storage_key, type, std::move(quota_client_types), modified)); +bool MockQuotaManager::AddBucket(const BucketInfo& bucket, + QuotaClientTypes quota_client_types, + base::Time modified) { + auto it = std::find_if( + buckets_.begin(), buckets_.end(), + [bucket](const BucketData& bucket_data) { + return bucket.id == bucket_data.bucket.id || + (bucket.name == bucket_data.bucket.name && + bucket.storage_key == bucket_data.bucket.storage_key && + bucket.type == bucket_data.bucket.type); + }); + DCHECK(it == buckets_.end()); + buckets_.emplace_back( + BucketData(bucket, std::move(quota_client_types), modified)); return true; } -bool MockQuotaManager::StorageKeyHasData(const StorageKey& storage_key, - StorageType type, - QuotaClientType quota_client) const { - for (const auto& info : storage_keys_) { - if (info.storage_key == storage_key && info.type == type && - info.quota_client_types.contains(quota_client)) +BucketInfo MockQuotaManager::CreateBucket(const StorageKey& storage_key, + const std::string& name, + StorageType type) { + return BucketInfo(bucket_id_generator_.GenerateNextId(), storage_key, type, + name, /*expiration=*/base::Time::Max(), /*quota=*/0); +} + +bool MockQuotaManager::BucketHasData(const BucketInfo& bucket, + QuotaClientType quota_client) const { + for (const auto& info : buckets_) { + if (info.bucket == bucket && info.quota_client_types.contains(quota_client)) return true; } return false; } -void MockQuotaManager::GetStorageKeysModifiedBetween( - StorageType type, - base::Time begin, - base::Time end, - GetStorageKeysCallback callback) { - auto storage_keys_to_return = std::make_unique<std::set<StorageKey>>(); - for (const auto& info : storage_keys_) { - if (info.type == type && info.modified >= begin && info.modified < end) - storage_keys_to_return->insert(info.storage_key); +int MockQuotaManager::BucketDataCount(QuotaClientType quota_client) { + return std::count_if( + buckets_.begin(), buckets_.end(), + [quota_client](const BucketData& bucket) { + return bucket.quota_client_types.contains(quota_client); + }); +} + +void MockQuotaManager::GetBucketsModifiedBetween(StorageType type, + base::Time begin, + base::Time end, + GetBucketsCallback callback) { + auto buckets_to_return = std::make_unique<std::set<BucketInfo>>(); + for (const auto& info : buckets_) { + if (info.bucket.type == type && info.modified >= begin && + info.modified < end) + buckets_to_return->insert(info.bucket); } base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&MockQuotaManager::DidGetModifiedInTimeRange, weak_factory_.GetWeakPtr(), std::move(callback), - std::move(storage_keys_to_return), type)); + std::move(buckets_to_return), type)); } -void MockQuotaManager::DeleteStorageKeyData(const StorageKey& storage_key, - StorageType type, - QuotaClientTypes quota_client_types, - StatusCallback callback) { - for (auto current = storage_keys_.begin(); current != storage_keys_.end(); - ++current) { - if (current->storage_key == storage_key && current->type == type) { +void MockQuotaManager::DeleteBucketData(const BucketInfo& bucket, + QuotaClientTypes quota_client_types, + StatusCallback callback) { + for (auto current = buckets_.begin(); current != buckets_.end(); ++current) { + if (current->bucket == bucket) { // Modify the mask: if it's 0 after "deletion", remove the storage key. for (QuotaClientType type : quota_client_types) current->quota_client_types.erase(type); if (current->quota_client_types.empty()) - storage_keys_.erase(current); + buckets_.erase(current); break; } } @@ -143,10 +159,10 @@ } void MockQuotaManager::DidGetModifiedInTimeRange( - GetStorageKeysCallback callback, - std::unique_ptr<std::set<StorageKey>> storage_keys, + GetBucketsCallback callback, + std::unique_ptr<std::set<BucketInfo>> buckets, StorageType storage_type) { - std::move(callback).Run(*storage_keys, storage_type); + std::move(callback).Run(*buckets, storage_type); } void MockQuotaManager::DidDeleteStorageKeyData(
diff --git a/storage/browser/test/mock_quota_manager.h b/storage/browser/test/mock_quota_manager.h index 95bd3b4..acbaaed 100644 --- a/storage/browser/test/mock_quota_manager.h +++ b/storage/browser/test/mock_quota_manager.h
@@ -53,20 +53,19 @@ // allows clients to set up the storage key database that should be queried. // This method will only search through the storage keys added explicitly via // AddStorageKey. - void GetStorageKeysModifiedBetween(blink::mojom::StorageType type, - base::Time begin, - base::Time end, - GetStorageKeysCallback callback) override; + void GetBucketsModifiedBetween(blink::mojom::StorageType type, + base::Time begin, + base::Time end, + GetBucketsCallback callback) override; - // Removes an storage key from the canned list of storage keys, but doesn't - // touch anything on disk. The caller must provide `quota_client_types` which + // Removes a bucket from the canned list of buckets, but doesn't touch + // anything on disk. The caller must provide `quota_client_types` which // specifies the types of QuotaClients which should be removed from this - // storage key. Setting the mask to AllQuotaClientTypes() will remove all - // clients from the storage key, regardless of type. - void DeleteStorageKeyData(const blink::StorageKey& storage_key, - blink::mojom::StorageType type, - QuotaClientTypes quota_client_types, - StatusCallback callback) override; + // bucket. Setting the mask to AllQuotaClientTypes() will remove all + // clients from the bucket, regardless of type. + void DeleteBucketData(const BucketInfo& bucket, + QuotaClientTypes quota_client_types, + StatusCallback callback) override; // Overrides QuotaManager's implementation so that tests can observe // calls to this function. @@ -78,23 +77,31 @@ int64_t quota); // Helper methods for timed-deletion testing: - // Adds a storage key to the canned list that will be searched through via - // GetStorageKeysModifiedBetween. - // `quota_clients` specified the types of QuotaClients this canned storage key + // Adds a bucket to the canned list that will be searched through via + // GetBucketsModifiedBetween. + // `quota_clients` specified the types of QuotaClients this canned bucket // contains. - bool AddStorageKey(const blink::StorageKey& storage_key, - blink::mojom::StorageType type, - QuotaClientTypes quota_client_types, - base::Time modified); + bool AddBucket(const BucketInfo& bucket, + QuotaClientTypes quota_client_types, + base::Time modified); + + // Creates a BucketInfo object with a generated BucketId. Makes sure newly + // created buckets are created with a unique id and with the specified + // attributes. + BucketInfo CreateBucket(const blink::StorageKey& storage_key, + const std::string& name, + blink::mojom::StorageType type); // Helper methods for timed-deletion testing: - // Checks an storage key and type against the storage keys that have been - // added via AddStorageKey and removed via DeleteStorageKeyData. If the - // storage key exists in the canned list with the proper StorageType and - // client, returns true. - bool StorageKeyHasData(const blink::StorageKey& storage_key, - blink::mojom::StorageType type, - QuotaClientType quota_client_type) const; + // Checks a bucket against the buckets that have been added via AddBucket and + // removed via DeleteBucketData. If the bucket exists in the canned list with + // the proper client, returns true. + bool BucketHasData(const BucketInfo& bucket, + QuotaClientType quota_client_type) const; + + // Returns the count for how many buckets still exist for the client to make + // sure there are no buckets that aren't accounted for during testing. + int BucketDataCount(QuotaClientType quota_client); std::map<const blink::StorageKey, int> write_error_tracker() const { return write_error_tracker_; @@ -106,24 +113,23 @@ private: friend class MockQuotaManagerProxy; - // Contains the essential bits of information about an storage key that the + // Contains the essential bits of information about a bucket that the // MockQuotaManager needs to understand for time-based deletion: - // the storage key itself, the StorageType and its modification time. - struct StorageKeyInfo { - StorageKeyInfo(const blink::StorageKey& storage_key, - blink::mojom::StorageType type, - QuotaClientTypes quota_clients, - base::Time modified); - ~StorageKeyInfo(); + // the bucket itself, the StorageType, its modification time and its + // QuotaClients. + struct BucketData { + BucketData(const BucketInfo& bucket, + QuotaClientTypes quota_clients, + base::Time modified); + ~BucketData(); - StorageKeyInfo(const StorageKeyInfo&) = delete; - StorageKeyInfo& operator=(const StorageKeyInfo&) = delete; + BucketData(const BucketData&) = delete; + BucketData& operator=(const BucketData&) = delete; - StorageKeyInfo(StorageKeyInfo&&); - StorageKeyInfo& operator=(StorageKeyInfo&&); + BucketData(BucketData&&); + BucketData& operator=(BucketData&&); - blink::StorageKey storage_key; - blink::mojom::StorageType type; + BucketInfo bucket; QuotaClientTypes quota_client_types; base::Time modified; }; @@ -144,15 +150,16 @@ void UpdateUsage(const blink::StorageKey& storage_key, blink::mojom::StorageType type, int64_t delta); - void DidGetModifiedInTimeRange( - GetStorageKeysCallback callback, - std::unique_ptr<std::set<blink::StorageKey>> storage_keys, - blink::mojom::StorageType storage_type); + void DidGetModifiedInTimeRange(GetBucketsCallback callback, + std::unique_ptr<std::set<BucketInfo>> buckets, + blink::mojom::StorageType storage_type); void DidDeleteStorageKeyData(StatusCallback callback, blink::mojom::QuotaStatusCode status); - // The list of stored storage keys that have been added via AddStorageKey. - std::vector<StorageKeyInfo> storage_keys_; + BucketId::Generator bucket_id_generator_; + + // The list of stored buckets that have been added via AddBucket. + std::vector<BucketData> buckets_; std::map<std::pair<blink::StorageKey, blink::mojom::StorageType>, StorageInfo> usage_and_quota_map_;
diff --git a/storage/browser/test/mock_quota_manager_unittest.cc b/storage/browser/test/mock_quota_manager_unittest.cc index 3ba6b82..e2c8ec7 100644 --- a/storage/browser/test/mock_quota_manager_unittest.cc +++ b/storage/browser/test/mock_quota_manager_unittest.cc
@@ -52,33 +52,38 @@ base::RunLoop().RunUntilIdle(); } - void GetModifiedStorageKeys(StorageType type, - base::Time begin, - base::Time end) { - manager_->GetStorageKeysModifiedBetween( + void GetModifiedBuckets(StorageType type, base::Time begin, base::Time end) { + base::RunLoop run_loop; + manager_->GetBucketsModifiedBetween( type, begin, end, - base::BindOnce(&MockQuotaManagerTest::GotModifiedStorageKeys, - weak_factory_.GetWeakPtr())); + base::BindOnce(&MockQuotaManagerTest::GotModifiedBuckets, + weak_factory_.GetWeakPtr(), run_loop.QuitClosure())); + run_loop.Run(); } - void GotModifiedStorageKeys(const std::set<StorageKey>& storage_keys, - StorageType type) { - storage_keys_ = storage_keys; + void GotModifiedBuckets(base::OnceClosure quit_closure, + const std::set<BucketInfo>& buckets, + StorageType type) { + buckets_ = buckets; type_ = type; + std::move(quit_closure).Run(); } - void DeleteStorageKeyData(const StorageKey& storage_key, - StorageType type, - QuotaClientTypes quota_client_types) { - manager_->DeleteStorageKeyData( - storage_key, type, std::move(quota_client_types), - base::BindOnce(&MockQuotaManagerTest::DeletedStorageKeyData, - weak_factory_.GetWeakPtr())); + void DeleteBucketData(const BucketInfo& bucket, + QuotaClientTypes quota_client_types) { + base::RunLoop run_loop; + manager_->DeleteBucketData( + bucket, std::move(quota_client_types), + base::BindOnce(&MockQuotaManagerTest::DeletedBucketData, + weak_factory_.GetWeakPtr(), run_loop.QuitClosure())); + run_loop.Run(); } - void DeletedStorageKeyData(blink::mojom::QuotaStatusCode status) { + void DeletedBucketData(base::OnceClosure quit_closure, + blink::mojom::QuotaStatusCode status) { ++deletion_callback_count_; EXPECT_EQ(blink::mojom::QuotaStatusCode::kOk, status); + std::move(quit_closure).Run(); } int deletion_callback_count() const { @@ -89,7 +94,7 @@ return manager_.get(); } - const std::set<StorageKey>& storage_keys() const { return storage_keys_; } + const std::set<BucketInfo>& buckets() const { return buckets_; } const StorageType& type() const { return type_; @@ -103,7 +108,7 @@ int deletion_callback_count_; - std::set<StorageKey> storage_keys_; + std::set<BucketInfo> buckets_; StorageType type_; base::WeakPtrFactory<MockQuotaManagerTest> weak_factory_{this}; @@ -111,183 +116,134 @@ DISALLOW_COPY_AND_ASSIGN(MockQuotaManagerTest); }; -TEST_F(MockQuotaManagerTest, BasicStorageKeyManipulation) { +TEST_F(MockQuotaManagerTest, BasicBucketManipulation) { const StorageKey kStorageKey1 = StorageKey::CreateFromStringForTesting("http://host1:1/"); const StorageKey kStorageKey2 = StorageKey::CreateFromStringForTesting("http://host2:1/"); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientDB)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientDB)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientDB)); + const BucketInfo temp_bucket1 = + manager()->CreateBucket(kStorageKey1, "temp_host1", kTemporary); + const BucketInfo perm_bucket1 = + manager()->CreateBucket(kStorageKey1, "perm_host1", kPersistent); + const BucketInfo temp_bucket2 = + manager()->CreateBucket(kStorageKey2, "temp_host2", kTemporary); + const BucketInfo perm_bucket2 = + manager()->CreateBucket(kStorageKey2, "perm_host2", kPersistent); - manager()->AddStorageKey(kStorageKey1, kTemporary, {kClientFile}, - base::Time::Now()); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientDB)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientDB)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientDB)); + EXPECT_EQ(manager()->BucketDataCount(kClientFile), 0); + EXPECT_EQ(manager()->BucketDataCount(kClientDB), 0); - manager()->AddStorageKey(kStorageKey1, kPersistent, {kClientFile}, - base::Time::Now()); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientDB)); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientDB)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientDB)); + manager()->AddBucket(temp_bucket1, {kClientFile}, base::Time::Now()); + EXPECT_EQ(manager()->BucketDataCount(kClientFile), 1); + EXPECT_EQ(manager()->BucketDataCount(kClientDB), 0); + EXPECT_TRUE(manager()->BucketHasData(temp_bucket1, kClientFile)); - manager()->AddStorageKey(kStorageKey2, kTemporary, {kClientFile, kClientDB}, - base::Time::Now()); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientDB)); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientDB)); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile)); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientDB)); + manager()->AddBucket(perm_bucket1, {kClientFile}, base::Time::Now()); + EXPECT_EQ(manager()->BucketDataCount(kClientFile), 2); + EXPECT_EQ(manager()->BucketDataCount(kClientDB), 0); + EXPECT_TRUE(manager()->BucketHasData(temp_bucket1, kClientFile)); + EXPECT_TRUE(manager()->BucketHasData(perm_bucket1, kClientFile)); + + manager()->AddBucket(temp_bucket2, {kClientFile, kClientDB}, + base::Time::Now()); + EXPECT_EQ(manager()->BucketDataCount(kClientFile), 3); + EXPECT_EQ(manager()->BucketDataCount(kClientDB), 1); + EXPECT_TRUE(manager()->BucketHasData(temp_bucket1, kClientFile)); + EXPECT_TRUE(manager()->BucketHasData(perm_bucket1, kClientFile)); + EXPECT_TRUE(manager()->BucketHasData(temp_bucket2, kClientFile)); + EXPECT_TRUE(manager()->BucketHasData(temp_bucket2, kClientDB)); + + manager()->AddBucket(perm_bucket2, {kClientDB}, base::Time::Now()); + EXPECT_EQ(manager()->BucketDataCount(kClientFile), 3); + EXPECT_EQ(manager()->BucketDataCount(kClientDB), 2); + EXPECT_TRUE(manager()->BucketHasData(temp_bucket1, kClientFile)); + EXPECT_TRUE(manager()->BucketHasData(perm_bucket1, kClientFile)); + EXPECT_TRUE(manager()->BucketHasData(temp_bucket2, kClientFile)); + EXPECT_TRUE(manager()->BucketHasData(temp_bucket2, kClientDB)); + EXPECT_TRUE(manager()->BucketHasData(perm_bucket2, kClientDB)); } -TEST_F(MockQuotaManagerTest, StorageKeyDeletion) { - const StorageKey kStorageKey1 = - StorageKey::CreateFromStringForTesting("http://host1:1/"); - const StorageKey kStorageKey2 = - StorageKey::CreateFromStringForTesting("http://host2:1/"); - const StorageKey kStorageKey3 = - StorageKey::CreateFromStringForTesting("http://host3:1/"); +TEST_F(MockQuotaManagerTest, BucketDeletion) { + const BucketInfo bucket1 = manager()->CreateBucket( + StorageKey::CreateFromStringForTesting("http://host1:1/"), + kDefaultBucketName, kTemporary); + const BucketInfo bucket2 = manager()->CreateBucket( + StorageKey::CreateFromStringForTesting("http://host2:1/"), + kDefaultBucketName, kPersistent); + const BucketInfo bucket3 = manager()->CreateBucket( + StorageKey::CreateFromStringForTesting("http://host3:1/"), + kDefaultBucketName, kTemporary); - manager()->AddStorageKey(kStorageKey1, kTemporary, {kClientFile}, - base::Time::Now()); - manager()->AddStorageKey(kStorageKey2, kTemporary, {kClientFile, kClientDB}, - base::Time::Now()); - manager()->AddStorageKey(kStorageKey3, kTemporary, {kClientFile, kClientDB}, - base::Time::Now()); + manager()->AddBucket(bucket1, {kClientFile}, base::Time::Now()); + manager()->AddBucket(bucket2, {kClientFile, kClientDB}, base::Time::Now()); + manager()->AddBucket(bucket3, {kClientFile, kClientDB}, base::Time::Now()); - DeleteStorageKeyData(kStorageKey2, kTemporary, {kClientFile}); - base::RunLoop().RunUntilIdle(); + DeleteBucketData(bucket2, {kClientFile}); EXPECT_EQ(1, deletion_callback_count()); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile)); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB)); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey3, kTemporary, kClientFile)); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey3, kTemporary, kClientDB)); + EXPECT_EQ(manager()->BucketDataCount(kClientFile), 2); + EXPECT_EQ(manager()->BucketDataCount(kClientDB), 2); + EXPECT_TRUE(manager()->BucketHasData(bucket1, kClientFile)); + EXPECT_TRUE(manager()->BucketHasData(bucket2, kClientDB)); + EXPECT_TRUE(manager()->BucketHasData(bucket3, kClientFile)); + EXPECT_TRUE(manager()->BucketHasData(bucket3, kClientDB)); - DeleteStorageKeyData(kStorageKey3, kTemporary, {kClientFile, kClientDB}); - base::RunLoop().RunUntilIdle(); + DeleteBucketData(bucket3, {kClientFile, kClientDB}); EXPECT_EQ(2, deletion_callback_count()); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile)); - EXPECT_TRUE( - manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey3, kTemporary, kClientFile)); - EXPECT_FALSE( - manager()->StorageKeyHasData(kStorageKey3, kTemporary, kClientDB)); + EXPECT_EQ(manager()->BucketDataCount(kClientFile), 1); + EXPECT_EQ(manager()->BucketDataCount(kClientDB), 1); + EXPECT_TRUE(manager()->BucketHasData(bucket1, kClientFile)); + EXPECT_TRUE(manager()->BucketHasData(bucket2, kClientDB)); } -TEST_F(MockQuotaManagerTest, ModifiedStorageKeys) { - const StorageKey kStorageKey1 = - StorageKey::CreateFromStringForTesting("http://host1:1/"); - const StorageKey kStorageKey2 = - StorageKey::CreateFromStringForTesting("http://host2:1/"); +TEST_F(MockQuotaManagerTest, ModifiedBuckets) { + const BucketInfo bucket1 = manager()->CreateBucket( + StorageKey::CreateFromStringForTesting("http://host1:1/"), + kDefaultBucketName, kTemporary); + const BucketInfo bucket2 = manager()->CreateBucket( + StorageKey::CreateFromStringForTesting("http://host2:1/"), + kDefaultBucketName, kTemporary); base::Time now = base::Time::Now(); base::Time then = base::Time(); base::TimeDelta an_hour = base::TimeDelta::FromMilliseconds(3600000); base::TimeDelta a_minute = base::TimeDelta::FromMilliseconds(60000); - GetModifiedStorageKeys(kTemporary, then, base::Time::Max()); - base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(storage_keys().empty()); + GetModifiedBuckets(kTemporary, then, base::Time::Max()); + EXPECT_TRUE(buckets().empty()); - manager()->AddStorageKey(kStorageKey1, kTemporary, {kClientFile}, - now - an_hour); + manager()->AddBucket(bucket1, {kClientFile}, now - an_hour); - GetModifiedStorageKeys(kTemporary, then, base::Time::Max()); - base::RunLoop().RunUntilIdle(); + GetModifiedBuckets(kTemporary, then, base::Time::Max()); EXPECT_EQ(kTemporary, type()); - EXPECT_EQ(1UL, storage_keys().size()); - EXPECT_EQ(1UL, storage_keys().count(kStorageKey1)); - EXPECT_EQ(0UL, storage_keys().count(kStorageKey2)); + EXPECT_EQ(1UL, buckets().size()); + EXPECT_EQ(1UL, buckets().count(bucket1)); + EXPECT_EQ(0UL, buckets().count(bucket2)); - manager()->AddStorageKey(kStorageKey2, kTemporary, {kClientFile}, now); + manager()->AddBucket(bucket2, {kClientFile}, now); - GetModifiedStorageKeys(kTemporary, then, base::Time::Max()); - base::RunLoop().RunUntilIdle(); + GetModifiedBuckets(kTemporary, then, base::Time::Max()); EXPECT_EQ(kTemporary, type()); - EXPECT_EQ(2UL, storage_keys().size()); - EXPECT_EQ(1UL, storage_keys().count(kStorageKey1)); - EXPECT_EQ(1UL, storage_keys().count(kStorageKey2)); + EXPECT_EQ(2UL, buckets().size()); + EXPECT_EQ(1UL, buckets().count(bucket1)); + EXPECT_EQ(1UL, buckets().count(bucket2)); - GetModifiedStorageKeys(kTemporary, then, now); - base::RunLoop().RunUntilIdle(); + GetModifiedBuckets(kTemporary, then, now); EXPECT_EQ(kTemporary, type()); - EXPECT_EQ(1UL, storage_keys().size()); - EXPECT_EQ(1UL, storage_keys().count(kStorageKey1)); - EXPECT_EQ(0UL, storage_keys().count(kStorageKey2)); + EXPECT_EQ(1UL, buckets().size()); + EXPECT_EQ(1UL, buckets().count(bucket1)); + EXPECT_EQ(0UL, buckets().count(bucket2)); - GetModifiedStorageKeys(kTemporary, now - a_minute, now + a_minute); - base::RunLoop().RunUntilIdle(); + GetModifiedBuckets(kTemporary, now - a_minute, now + a_minute); EXPECT_EQ(kTemporary, type()); - EXPECT_EQ(1UL, storage_keys().size()); - EXPECT_EQ(0UL, storage_keys().count(kStorageKey1)); - EXPECT_EQ(1UL, storage_keys().count(kStorageKey2)); + EXPECT_EQ(1UL, buckets().size()); + EXPECT_EQ(0UL, buckets().count(bucket1)); + EXPECT_EQ(1UL, buckets().count(bucket2)); } } // namespace storage
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json index 0657d8e..e91d9fc 100644 --- a/testing/buildbot/chromium.android.fyi.json +++ b/testing/buildbot/chromium.android.fyi.json
@@ -11055,7 +11055,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M92", - "revision": "version:92.0.4515.135" + "revision": "version:92.0.4515.137" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -11142,7 +11142,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M93", - "revision": "version:93.0.4577.23" + "revision": "version:93.0.4577.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -11316,7 +11316,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M92", - "revision": "version:92.0.4515.135" + "revision": "version:92.0.4515.137" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -11403,7 +11403,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M93", - "revision": "version:93.0.4577.23" + "revision": "version:93.0.4577.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json index 86fff54c..4e0c397c 100644 --- a/testing/buildbot/chromium.android.json +++ b/testing/buildbot/chromium.android.json
@@ -50487,7 +50487,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M92", - "revision": "version:92.0.4515.135" + "revision": "version:92.0.4515.137" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -50575,7 +50575,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M93", - "revision": "version:93.0.4577.23" + "revision": "version:93.0.4577.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -50751,7 +50751,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M92", - "revision": "version:92.0.4515.135" + "revision": "version:92.0.4515.137" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -50839,7 +50839,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M93", - "revision": "version:93.0.4577.23" + "revision": "version:93.0.4577.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -51088,7 +51088,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M92", - "revision": "version:92.0.4515.135" + "revision": "version:92.0.4515.137" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -51175,7 +51175,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M93", - "revision": "version:93.0.4577.23" + "revision": "version:93.0.4577.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -51349,7 +51349,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M92", - "revision": "version:92.0.4515.135" + "revision": "version:92.0.4515.137" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -51436,7 +51436,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M93", - "revision": "version:93.0.4577.23" + "revision": "version:93.0.4577.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -51685,7 +51685,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M92", - "revision": "version:92.0.4515.135" + "revision": "version:92.0.4515.137" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -51772,7 +51772,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M93", - "revision": "version:93.0.4577.23" + "revision": "version:93.0.4577.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -51946,7 +51946,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M92", - "revision": "version:92.0.4515.135" + "revision": "version:92.0.4515.137" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -52033,7 +52033,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M93", - "revision": "version:93.0.4577.23" + "revision": "version:93.0.4577.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl index 8019fe93..fb0557a 100644 --- a/testing/buildbot/variants.pyl +++ b/testing/buildbot/variants.pyl
@@ -349,7 +349,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M93', - 'revision': 'version:93.0.4577.23', + 'revision': 'version:93.0.4577.25', } ], }, @@ -373,7 +373,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M92', - 'revision': 'version:92.0.4515.135', + 'revision': 'version:92.0.4515.137', } ], }, @@ -421,7 +421,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M93', - 'revision': 'version:93.0.4577.23', + 'revision': 'version:93.0.4577.25', } ], }, @@ -445,7 +445,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M92', - 'revision': 'version:92.0.4515.135', + 'revision': 'version:92.0.4515.137', } ], }, @@ -493,7 +493,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M93', - 'revision': 'version:93.0.4577.23', + 'revision': 'version:93.0.4577.25', } ], }, @@ -517,7 +517,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M92', - 'revision': 'version:92.0.4515.135', + 'revision': 'version:92.0.4515.137', } ], }, @@ -626,4 +626,4 @@ ], }, }, -} +} \ No newline at end of file
diff --git a/testing/scripts/run_variations_smoke_tests.py b/testing/scripts/run_variations_smoke_tests.py index 0e34d76..ac8ee34 100755 --- a/testing/scripts/run_variations_smoke_tests.py +++ b/testing/scripts/run_variations_smoke_tests.py
@@ -3,27 +3,221 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """A smoke test to verify Chrome doesn't crash and basic rendering is functional -when parsing a newly given Finch seed. +when parsing a newly given variations seed. """ import argparse import json import logging import os +import shutil import sys +import tempfile import common +_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +_SRC_DIR = os.path.join(_THIS_DIR, os.path.pardir, os.path.pardir) +_WEBDRIVER_PATH = os.path.join(_SRC_DIR, 'third_party', 'webdriver', 'pylib') + +sys.path.insert(0, _WEBDRIVER_PATH) +from selenium import webdriver +from selenium.webdriver import ChromeOptions +from selenium.common.exceptions import NoSuchElementException +from selenium.common.exceptions import WebDriverException + +# Constants around the Local State file and variation keys. +_LOCAL_STATE_FILE_NAME = 'Local State' +_LOCAL_STATE_SEED_NAME = 'variations_compressed_seed' +_LOCAL_STATE_SEED_SIGNATURE_NAME = 'variations_seed_signature' + +# Test cases to verify web elements can be rendered correctly. +_TEST_CASES = [ + { + # data:text/html,<h1 id="success">Success</h1> + 'url': 'data:text/html,%3Ch1%20id%3D%22success%22%3ESuccess%3C%2Fh1%3E', + 'expected_id': 'success', + 'expected_text': 'Success', + }, + { + # TODO(crbug.com/1234165): Make tests hermetic by using a test http + # server or WPR. + 'url': 'https://chromium.org/', + 'expected_id': 'sites-chrome-userheader-title', + 'expected_text': 'The Chromium Projects', + }, +] + + +def _parse_test_seed(): + """Reads and parses the test variations seed. + + For prototypeing propose, a test seed is hard-coded in the + variations_smoke_test_data/ directory, and this function should be updated to + use the seed provided by the official variations test recipe once it's ready + for integration. + + Returns: + A tuple of two strings: the compressed seed and the seed signature. + """ + with open( + os.path.join(_THIS_DIR, 'variations_smoke_test_data', + 'variations_seed_beta_linux.json')) as f: + seed_json = json.load(f) + + return (seed_json.get(_LOCAL_STATE_SEED_NAME, None), + seed_json.get(_LOCAL_STATE_SEED_NAME, None)) + + +def _get_current_seed(user_data_dir): + """Gets the current seed. + + Args: + user_data_dir (str): Path to the user data directory used to laucn Chrome. + + Returns: + A tuple of two strings: the compressed seed and the seed signature. + """ + with open(os.path.join(user_data_dir, _LOCAL_STATE_FILE_NAME)) as f: + local_state = json.load(f) + + return local_state.get(_LOCAL_STATE_SEED_NAME, None), local_state.get( + _LOCAL_STATE_SEED_SIGNATURE_NAME, None) + + +def _inject_test_seed(seed, signature, user_data_dir): + """Injects the given test seed. + + Args: + seed (str): A variations seed. + signature (str): A seed signature. + user_data_dir (str): Path to the user data directory used to laucn Chrome. + """ + with open(os.path.join(user_data_dir, _LOCAL_STATE_FILE_NAME)) as f: + local_state = json.load(f) + + local_state[_LOCAL_STATE_SEED_NAME] = seed + local_state[_LOCAL_STATE_SEED_SIGNATURE_NAME] = signature + + with open(os.path.join(user_data_dir, _LOCAL_STATE_FILE_NAME), 'w') as f: + json.dump(local_state, f) + + +def _run_tests(): + """Runs the smoke tests. + + Returns: + 0 if tests passed, otherwise 1. + """ + path_chrome = os.path.join('.', 'chrome') + path_chromedriver = os.path.join('.', 'chromedriver') + + user_data_dir = tempfile.mkdtemp() + _, log_file = tempfile.mkstemp() + + chrome_options = ChromeOptions() + chrome_options.binary_location = path_chrome + chrome_options.add_argument('user-data-dir=' + user_data_dir) + chrome_options.add_argument('log-file=' + log_file) + + # By default, ChromeDriver passes in --disable-backgroud-networking, however, + # fetching variations seeds requires network connection, so override it. + chrome_options.add_experimental_option('excludeSwitches', + ['disable-background-networking']) + + driver = None + try: + # Starts Chrome without seed. + driver = webdriver.Chrome(path_chromedriver, chrome_options=chrome_options) + driver.close() + + # Verify a production version of variations seed was fetched successfully. + current_seed, current_signature = _get_current_seed(user_data_dir) + if not current_seed or not current_signature: + logging.error('Failed to fetch variations seed on initial run') + return 1 + + # Inject the test seed. + seed, signature = _parse_test_seed() + if not seed or not signature: + logging.error( + 'Ill-formed test seed json file: "%s" and "%s" are required', + _LOCAL_STATE_SEED_NAME, _LOCAL_STATE_SEED_SIGNATURE_NAME) + return 1 + + _inject_test_seed(seed, signature, user_data_dir) + + # Verify the seed has been injected successfully. + current_seed, current_signature = _get_current_seed(user_data_dir) + if current_seed != seed or current_signature != signature: + logging.error('Failed to inject the test seed') + return 1 + + # Starts Chrome again with the test seed injected. + driver = webdriver.Chrome(path_chromedriver, chrome_options=chrome_options) + + # Run test cases: visit urls and verify certain web elements are rendered + # correctly. + # TODO(crbug.com/1234404): Investigate pixel/layout based testing instead of + # DOM based testing to verify that rendering is working properly. + for t in _TEST_CASES: + driver.get(t['url']) + element = driver.find_element_by_id(t['expected_id']) + if not element.is_displayed() or t['expected_text'] != element.text: + logging.error( + 'Test failed because element: "%s" is not visibly found after ' + 'visiting url: "%s"', t['expected_text'], t['url']) + return 1 + + driver.close() + + # Starts Chrome again to allow it to download a seed delta and update the + # seed with it. + driver = webdriver.Chrome(path_chromedriver, chrome_options=chrome_options) + driver.close() + + # Verify seed has been updated successfully and it's different from the + # injected test seed. + # + # TODO(crbug.com/1234171): This test expectation may not work correctly when + # a field trial config under test does not affect a platform, so it requires + # more investigations to figure out the correct behavior. + current_seed, current_signature = _get_current_seed(user_data_dir) + if current_seed == seed or current_signature == signature: + logging.error('Failed to update seed with a delta') + return 1 + except WebDriverException as e: + logging.error('Chrome exited abnormally, likely due to a crash.\n%s', e) + return 1 + except NoSuchElementException as e: + logging.error('Failed to find the expected web element.\n%s', e) + return 1 + finally: + shutil.rmtree(user_data_dir, ignore_errors=True) + + # Print logs for debugging purpose. + with open(log_file) as f: + logging.info('Chrome logs for debugging:\n%s', f.read()) + + shutil.rmtree(log_file, ignore_errors=True) + if driver: + driver.quit() + + return 0 + def main_run(args): - """Runs the Finch smoke tests.""" + """Runs the variations smoke tests.""" logging.basicConfig(level=logging.INFO) parser = argparse.ArgumentParser() parser.add_argument('--isolated-script-test-output', type=str, required=True) args, _ = parser.parse_known_args() + rc = _run_tests() with open(args.isolated_script_test_output, 'w') as f: - common.record_local_script_results('run_finch_smoke_tests', f, [], True) - return 0 + common.record_local_script_results('run_variations_smoke_tests', f, [], + rc == 0) + + return rc def main_compile_targets(args):
diff --git a/testing/scripts/variations_smoke_test_data/variations_seed_beta_linux.json b/testing/scripts/variations_smoke_test_data/variations_seed_beta_linux.json new file mode 100644 index 0000000..4cba279 --- /dev/null +++ b/testing/scripts/variations_smoke_test_data/variations_seed_beta_linux.json
@@ -0,0 +1 @@ +{"variations_compressed_seed":"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","variations_seed_signature":"MEYCIQDTF0+81NAyjSBmb9DA1BZs9thXcCBEaG+kx/Qa8rZAJwIhAMJAc4FL58G4VIUKYUBFJjSRHat7oAP/zC8Ho0Ua3OZP"} \ No newline at end of file
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index b75e286..66096f0 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -3928,6 +3928,25 @@ ] } ], + "GridAndGroupAndroidM5": [ + { + "platforms": [ + "android" + ], + "experiments": [ + { + "name": "Enabled", + "params": { + "enable_launch_bug_fix": "true", + "enable_launch_polish": "true" + }, + "enable_features": [ + "TabGroupsContinuationAndroid" + ] + } + ] + } + ], "GroupAutoCreationAndroid": [ { "platforms": [ @@ -5688,25 +5707,6 @@ ] } ], - "OmniboxPedalsBatch2": [ - { - "platforms": [ - "windows", - "mac", - "chromeos", - "linux" - ], - "experiments": [ - { - "name": "Batch2Colored", - "enable_features": [ - "OmniboxPedalsBatch2", - "OmniboxPedalsDefaultIconColored" - ] - } - ] - } - ], "OmniboxUpdatedConnectionSecurityIndicatorsIPH": [ { "platforms": [ @@ -7504,6 +7504,21 @@ ] } ], + "SafeBrowsingPasswordCheckIntegrationForSavedPasswordsAndroid": [ + { + "platforms": [ + "android" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "SafeBrowsingPasswordCheckIntegrationForSavedPasswordsAndroid" + ] + } + ] + } + ], "SafeBrowsingPasswordProtectionRequestWithToken": [ { "platforms": [
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc index 4d4de64..bdb5f10 100644 --- a/third_party/blink/renderer/core/dom/document.cc +++ b/third_party/blink/renderer/core/dom/document.cc
@@ -8112,6 +8112,11 @@ execution_context_->CountUse(feature); } +void Document::CountDeprecation(mojom::WebFeature feature) { + if (execution_context_) + execution_context_->CountDeprecation(feature); +} + void Document::CountProperty(CSSPropertyID property) const { if (DocumentLoader* loader = Loader()) { loader->GetUseCounter().Count(
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h index 62564aaa..5e8a699 100644 --- a/third_party/blink/renderer/core/dom/document.h +++ b/third_party/blink/renderer/core/dom/document.h
@@ -1606,6 +1606,7 @@ // Use counter related functions. void CountUse(mojom::WebFeature feature) final; + void CountDeprecation(mojom::WebFeature feature) final; void CountUse(mojom::WebFeature feature) const; void CountProperty(CSSPropertyID property_id) const; void CountAnimatedProperty(CSSPropertyID property_id) const;
diff --git a/third_party/blink/renderer/core/execution_context/execution_context.cc b/third_party/blink/renderer/core/execution_context/execution_context.cc index 181698e..20fa8ff 100644 --- a/third_party/blink/renderer/core/execution_context/execution_context.cc +++ b/third_party/blink/renderer/core/execution_context/execution_context.cc
@@ -180,6 +180,10 @@ ContextLifecycleNotifier::NotifyContextDestroyed(); } +void ExecutionContext::CountDeprecation(WebFeature feature) { + Deprecation::CountDeprecation(this, feature); +} + HeapObserverSet<ContextLifecycleObserver>& ExecutionContext::ContextLifecycleObserverSet() { return ContextLifecycleNotifier::observers();
diff --git a/third_party/blink/renderer/core/execution_context/execution_context.h b/third_party/blink/renderer/core/execution_context/execution_context.h index 5e8c470..7d5894c 100644 --- a/third_party/blink/renderer/core/execution_context/execution_context.h +++ b/third_party/blink/renderer/core/execution_context/execution_context.h
@@ -251,6 +251,8 @@ virtual void AddInspectorIssue(mojom::blink::InspectorIssueInfoPtr) = 0; virtual void AddInspectorIssue(AuditsIssue) = 0; + void CountDeprecation(WebFeature feature) override; + bool IsContextPaused() const; LoaderFreezeMode GetLoaderFreezeMode() const; bool IsContextDestroyed() const { return is_context_destroyed_; }
diff --git a/third_party/blink/renderer/core/frame/deprecation.cc b/third_party/blink/renderer/core/frame/deprecation.cc index de3093c5..ef37dc7 100644 --- a/third_party/blink/renderer/core/frame/deprecation.cc +++ b/third_party/blink/renderer/core/frame/deprecation.cc
@@ -586,6 +586,12 @@ case WebFeature::kXHRJSONEncodingDetection: return {"XHRJSONEncodingDetection", kM93, "UTF-16 is not supported by response json in XMLHttpRequest"}; + + case WebFeature::kAuthorizationCoveredByWildcard: + return {"AuthorizationCoveredByWildcard", kM97, + "\"Authorization\" will not be covered by the wildcard symbol (*)" + "in CORS \"Access-Control-Allow-Headers\" handling."}; + // Features that aren't deprecated don't have a deprecation message. default: return {"NotDeprecated", kUnknown, ""};
diff --git a/third_party/blink/renderer/core/frame/history.cc b/third_party/blink/renderer/core/frame/history.cc index 4ad6068..4de925a 100644 --- a/third_party/blink/renderer/core/frame/history.cc +++ b/third_party/blink/renderer/core/frame/history.cc
@@ -196,6 +196,7 @@ if (Page* page = DomWindow()->GetFrame()->GetPage()) page->HistoryNavigationVirtualTimePauser().PauseVirtualTime(); } + DomWindow()->document()->Loader()->DidTriggerBackForwardNavigation(); } else { // We intentionally call reload() for the current frame if delta is zero. // Otherwise, navigation happens on the root frame.
diff --git a/third_party/blink/renderer/core/html/resources/html.css b/third_party/blink/renderer/core/html/resources/html.css index 93f608e..5bc0aaf 100644 --- a/third_party/blink/renderer/core/html/resources/html.css +++ b/third_party/blink/renderer/core/html/resources/html.css
@@ -27,31 +27,6 @@ display: block; } -/* children of the <head> element all have display:none */ -head { - display: none -} - -meta { - display: none -} - -title { - display: none -} - -link { - display: none -} - -style { - display: none -} - -script { - display: none -} - /* generic block-level elements */ body { @@ -459,10 +434,6 @@ font-family: monospace; } -input[type="hidden" i] { - display: none -} - input { appearance: auto; padding:1px 0; @@ -801,22 +772,23 @@ outline-offset: -1px; } -datalist { - display: none -} - +/* https://html.spec.whatwg.org/multipage/rendering.html#hidden-elements */ +/* TODO(crbug.com/1231263): <area> should be display:none. */ area { - display: inline; + display: inline; +} +base, basefont, datalist, head, link, meta, noembed, +noframes, param, rp, script, style, template, title { + display: none; +} +input[type="hidden" i] { + display: none !important; } area:-webkit-any-link { cursor: pointer; } -param { - display: none -} - input[type="checkbox" i] { appearance: auto; box-sizing: border-box; @@ -1348,16 +1320,8 @@ text-align: start; } -rp { - display: none; -} - /* other elements */ -noframes { - display: none -} - frameset, frame { display: block } @@ -1397,10 +1361,6 @@ list-style-type: disclosure-open; } -template { - display: none -} - bdi, output { unicode-bidi: isolate; }
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc index 80e4adb..df939940 100644 --- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc +++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
@@ -1727,7 +1727,12 @@ // NGInlineItem. continue; } - if (item.Type() == NGInlineItem::kAtomicInline) { +#if DCHECK_IS_ON() + if (item.Type() == NGInlineItem::kBlockInInline) + DCHECK(line_info.HasForcedBreak()); +#endif + if (item.Type() == NGInlineItem::kAtomicInline || + item.Type() == NGInlineItem::kBlockInInline) { // The max-size for atomic inlines are cached in |max_size_cache|. unsigned item_index = &item - items_data.items.begin(); position += max_size_cache[item_index]; @@ -1747,10 +1752,6 @@ continue; } } -#if DCHECK_IS_ON() - if (item.Type() == NGInlineItem::kBlockInInline) - DCHECK(line_info.HasForcedBreak()); -#endif position += result.inline_size; } // Compute the forced break after all results were handled, because
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc index 0adff8f..5972df5 100644 --- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc +++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
@@ -1899,39 +1899,10 @@ item_result->layout_result->PhysicalFragment()) .InlineSize(); item_result->inline_size += inline_margins; - } else if (mode_ == NGLineBreakerMode::kMaxContent && max_size_cache_) { - unsigned item_index = &item - Items().begin(); - item_result->inline_size = (*max_size_cache_)[item_index]; } else { - DCHECK(mode_ == NGLineBreakerMode::kMinContent || !max_size_cache_); - NGBlockNode child(To<LayoutBox>(item.GetLayoutObject())); - - NGMinMaxConstraintSpaceBuilder builder(constraint_space_, node_.Style(), - child, /* is_new_fc */ true); - builder.SetAvailableBlockSize(constraint_space_.AvailableSize().block_size); - builder.SetPercentageResolutionBlockSize( - constraint_space_.PercentageResolutionBlockSize()); - builder.SetReplacedPercentageResolutionBlockSize( - constraint_space_.ReplacedPercentageResolutionBlockSize()); - const auto space = builder.ToConstraintSpace(); - - MinMaxSizesResult result = - ComputeMinAndMaxContentContribution(node_.Style(), child, space); - if (mode_ == NGLineBreakerMode::kMinContent) { - item_result->inline_size = result.sizes.min_size + inline_margins; - if (depends_on_block_constraints_out_) { - *depends_on_block_constraints_out_ |= - result.depends_on_block_constraints; - } - if (max_size_cache_) { - if (max_size_cache_->IsEmpty()) - max_size_cache_->resize(Items().size()); - unsigned item_index = &item - Items().begin(); - (*max_size_cache_)[item_index] = result.sizes.max_size + inline_margins; - } - } else { - item_result->inline_size = result.sizes.max_size + inline_margins; - } + DCHECK(mode_ == NGLineBreakerMode::kMaxContent || + mode_ == NGLineBreakerMode::kMinContent); + ComputeMinMaxContentSizeForBlockChild(item, item_result); } item_result->should_create_line_box = true; @@ -1958,6 +1929,49 @@ MoveToNextOf(item); } +void NGLineBreaker::ComputeMinMaxContentSizeForBlockChild( + const NGInlineItem& item, + NGInlineItemResult* item_result) { + DCHECK(mode_ == NGLineBreakerMode::kMaxContent || + mode_ == NGLineBreakerMode::kMinContent); + if (mode_ == NGLineBreakerMode::kMaxContent && max_size_cache_) { + const unsigned item_index = &item - Items().begin(); + item_result->inline_size = (*max_size_cache_)[item_index]; + return; + } + + DCHECK(mode_ == NGLineBreakerMode::kMinContent || !max_size_cache_); + NGBlockNode child(To<LayoutBox>(item.GetLayoutObject())); + + NGMinMaxConstraintSpaceBuilder builder(constraint_space_, node_.Style(), + child, /* is_new_fc */ true); + builder.SetAvailableBlockSize(constraint_space_.AvailableSize().block_size); + builder.SetPercentageResolutionBlockSize( + constraint_space_.PercentageResolutionBlockSize()); + builder.SetReplacedPercentageResolutionBlockSize( + constraint_space_.ReplacedPercentageResolutionBlockSize()); + const auto space = builder.ToConstraintSpace(); + + const MinMaxSizesResult result = + ComputeMinAndMaxContentContribution(node_.Style(), child, space); + const LayoutUnit inline_margins = item_result->margins.InlineSum(); + if (mode_ == NGLineBreakerMode::kMinContent) { + item_result->inline_size = result.sizes.min_size + inline_margins; + if (depends_on_block_constraints_out_) + *depends_on_block_constraints_out_ |= result.depends_on_block_constraints; + if (max_size_cache_) { + if (max_size_cache_->IsEmpty()) + max_size_cache_->resize(Items().size()); + const unsigned item_index = &item - Items().begin(); + (*max_size_cache_)[item_index] = result.sizes.max_size + inline_margins; + } + return; + } + + DCHECK(mode_ == NGLineBreakerMode::kMaxContent && !max_size_cache_); + item_result->inline_size = result.sizes.max_size + inline_margins; +} + void NGLineBreaker::HandleBlockInInline(const NGInlineItem& item, NGLineInfo* line_info) { DCHECK_EQ(item.Type(), NGInlineItem::kBlockInInline); @@ -1968,30 +1982,36 @@ return; } - // TODO(crbug.com/716930): Support MinMax. - NGInlineItemResult* item_result = AddItem(item, line_info); - const NGBlockBreakToken* block_break_token = - break_token_ ? break_token_->BlockInInlineBreakToken() : nullptr; - scoped_refptr<const NGLayoutResult> layout_result = - NGBlockNode(To<LayoutBox>(item.GetLayoutObject())) - .Layout(constraint_space_, block_break_token); - if (layout_result->Status() != NGLayoutResult::kSuccess) { - line_info->SetAbortedLayoutResult(std::move(layout_result)); - state_ = LineBreakState::kDone; - return; - } - const NGPhysicalFragment& fragment = layout_result->PhysicalFragment(); - item_result->inline_size = - NGFragment(constraint_space_.GetWritingDirection(), fragment) - .InlineSize(); - position_ += item_result->inline_size; - item_result->should_create_line_box = !layout_result->IsSelfCollapsing(); - item_result->layout_result = std::move(layout_result); + if (mode_ == NGLineBreakerMode::kContent) { + const NGBlockBreakToken* block_break_token = + break_token_ ? break_token_->BlockInInlineBreakToken() : nullptr; + scoped_refptr<const NGLayoutResult> layout_result = + NGBlockNode(To<LayoutBox>(item.GetLayoutObject())) + .Layout(constraint_space_, block_break_token); + if (layout_result->Status() != NGLayoutResult::kSuccess) { + line_info->SetAbortedLayoutResult(std::move(layout_result)); + state_ = LineBreakState::kDone; + return; + } + const NGPhysicalFragment& fragment = layout_result->PhysicalFragment(); + item_result->inline_size = + NGFragment(constraint_space_.GetWritingDirection(), fragment) + .InlineSize(); - DCHECK(!line_info->BlockInInlineBreakToken()); - line_info->SetBlockInInlineBreakToken( - To<NGBlockBreakToken>(fragment.BreakToken())); + item_result->should_create_line_box = !layout_result->IsSelfCollapsing(); + item_result->layout_result = std::move(layout_result); + + DCHECK(!line_info->BlockInInlineBreakToken()); + line_info->SetBlockInInlineBreakToken( + To<NGBlockBreakToken>(fragment.BreakToken())); + } else { + DCHECK(mode_ == NGLineBreakerMode::kMaxContent || + mode_ == NGLineBreakerMode::kMinContent); + ComputeMinMaxContentSizeForBlockChild(item, item_result); + } + + position_ += item_result->inline_size; line_info->SetIsBlockInInline(); line_info->SetHasForcedBreak(); is_after_forced_break_ = true;
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h index fc2de55..e17cbf0 100644 --- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h +++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
@@ -175,6 +175,8 @@ void HandleBidiControlItem(const NGInlineItem&, NGLineInfo*); void HandleAtomicInline(const NGInlineItem&, NGLineInfo*); void HandleBlockInInline(const NGInlineItem&, NGLineInfo*); + void ComputeMinMaxContentSizeForBlockChild(const NGInlineItem&, + NGInlineItemResult*); bool CanBreakAfterAtomicInline(const NGInlineItem& item) const; bool CanBreakAfter(const NGInlineItem& item) const;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc index 4dc13a4..31aebf7 100644 --- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc +++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -711,7 +711,10 @@ input.override_inline_size = fragment.InlineSize(); input.override_block_size = fragment.BlockSize(); box_->ComputeAndSetBlockDirectionMargins(box_->ContainingBlock()); - box_->ForceLayout(); + if (box_->NeedsLayout()) + box_->LayoutIfNeeded(); + else + box_->ForceLayout(); DCHECK_EQ(box_->Size(), physical_fragment.Size().ToLayoutSize()) << "Legacy layout was supposed to use the size that NG computed"; }
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc index 420e25ec..7bd0d58 100644 --- a/third_party/blink/renderer/core/loader/document_loader.cc +++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -315,6 +315,7 @@ WebVector<WebHistoryItem> app_history_forward_entries; mojo::Remote<blink::mojom::CodeCacheHost> code_cache_host; HashSet<KURL> early_hints_preloaded_resources_; + bool scroll_offset_changed_since_last_history_navigation_triggered_; }; // Asserts size of DocumentLoader, so that whenever a new attribute is added to @@ -642,6 +643,7 @@ GetLocalFrameClient().DidObserveInputDelay(input_delay); } } + void DocumentLoader::DidObserveLoadingBehavior(LoadingBehaviorFlag behavior) { if (frame_) { DCHECK_GE(state_, kCommitted); @@ -649,6 +651,18 @@ } } +void DocumentLoader::DidTriggerBackForwardNavigation() { + scroll_offset_changed_since_last_history_navigation_triggered_ = false; +} + +void DocumentLoader::DidChangeScrollOffset() { + // The scroll offset changed. If a pending history navigation commits in this + // document later on, we should keep the updated scroll offset instead of + // trying to restore the scroll offset from the session history entry. + // See also https://crbug.com/1209717. + scroll_offset_changed_since_last_history_navigation_triggered_ = true; +} + // static WebHistoryCommitType LoadTypeToCommitType(WebFrameLoadType type) { switch (type) { @@ -1396,6 +1410,10 @@ // to check again if frame_ is null. if (!frame_ || !frame_->GetPage()) return; + + bool may_restore_scroll_offset = + history_item && + !scroll_offset_changed_since_last_history_navigation_triggered_; GetFrameLoader().SaveScrollState(); KURL old_url = frame_->GetDocument()->Url(); @@ -1435,8 +1453,8 @@ // to a same ISN when a history navigation targets a frame that no longer // exists (https://crbug.com/705550). if (!same_item_sequence_number) { - GetFrameLoader().DidFinishSameDocumentNavigation(url, frame_load_type, - history_item); + GetFrameLoader().DidFinishSameDocumentNavigation( + url, frame_load_type, history_item, may_restore_scroll_offset); } } @@ -2566,6 +2584,10 @@ return use_counter_.Count(feature, GetFrame()); } +void DocumentLoader::CountDeprecation(mojom::WebFeature feature) { + return use_counter_.Count(feature, GetFrame()); +} + void DocumentLoader::RecordUseCountersForCommit() { // Pre-commit state, count usage the use counter associated with "this" // (provisional document loader) instead of frame_'s document loader.
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h index 5a01205..d12a375 100644 --- a/third_party/blink/renderer/core/loader/document_loader.h +++ b/third_party/blink/renderer/core/loader/document_loader.h
@@ -165,6 +165,9 @@ void DidObserveInputDelay(base::TimeDelta input_delay); void DidObserveLoadingBehavior(LoadingBehaviorFlag); + void DidTriggerBackForwardNavigation(); + void DidChangeScrollOffset(); + // https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps void RunURLAndHistoryUpdateSteps( const KURL&, @@ -304,6 +307,7 @@ // UseCounter void CountUse(mojom::WebFeature) override; + void CountDeprecation(mojom::WebFeature) override; void SetApplicationCacheHostForTesting(ApplicationCacheHostForFrame* host) { application_cache_host_ = host; @@ -650,6 +654,11 @@ mojo::Remote<blink::mojom::CodeCacheHost> code_cache_host_; HashSet<KURL> early_hints_preloaded_resources_; + + // Tracks whether the scroll offset changed since the last time a history + // navigation was triggered from this document, so that we won't attempt to do + // scroll restoration when the navigation commits. + bool scroll_offset_changed_since_last_history_navigation_triggered_ = false; }; DECLARE_WEAK_IDENTIFIER_MAP(DocumentLoader);
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc index 2d63d0d..9a9bdce 100644 --- a/third_party/blink/renderer/core/loader/frame_loader.cc +++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -293,8 +293,15 @@ // scroll offsets. In order to avoid keeping around a stale anchor, we clear // it when the saved scroll offset changes. history_item->SetScrollAnchorData(ScrollAnchorData()); - if (ScrollableArea* layout_scrollable_area = frame_->View()->LayoutViewport()) + if (ScrollableArea* layout_scrollable_area = + frame_->View()->LayoutViewport()) { + if (!history_item->GetViewState() || + layout_scrollable_area->GetScrollOffset() != + history_item->GetViewState()->scroll_offset_) { + document_loader_->DidChangeScrollOffset(); + } history_item->SetScrollOffset(layout_scrollable_area->GetScrollOffset()); + } history_item->SetVisualViewportScrollOffset(ToScrollOffset( frame_->GetPage()->GetVisualViewport().VisibleRect().Location())); @@ -422,7 +429,8 @@ void FrameLoader::DidFinishSameDocumentNavigation( const KURL& url, WebFrameLoadType frame_load_type, - HistoryItem* history_item) { + HistoryItem* history_item, + bool may_restore_scroll_offset) { // If we have a state object, we cannot also be a new navigation. scoped_refptr<SerializedScriptValue> state_object = history_item ? history_item->StateObject() : nullptr; @@ -438,7 +446,7 @@ ? std::move(state_object) : SerializedScriptValue::NullValue()); - if (view_state) { + if (view_state && may_restore_scroll_offset) { RestoreScrollPositionAndViewState(frame_load_type, *view_state, history_item->ScrollRestorationType()); }
diff --git a/third_party/blink/renderer/core/loader/frame_loader.h b/third_party/blink/renderer/core/loader/frame_loader.h index e5995b1..4c313f5 100644 --- a/third_party/blink/renderer/core/loader/frame_loader.h +++ b/third_party/blink/renderer/core/loader/frame_loader.h
@@ -177,7 +177,8 @@ void DidFinishSameDocumentNavigation(const KURL&, WebFrameLoadType, - HistoryItem*); + HistoryItem*, + bool may_restore_scroll_offset); // This will attempt to detach the current document. It will dispatch unload // events and abort XHR requests. Returns true if the frame is ready to
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc index 2e4a02b..8a176f4 100644 --- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc +++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -45,6 +45,7 @@ #include "third_party/blink/renderer/core/paint/paint_timing_detector.h" #include "third_party/blink/renderer/core/paint/rounded_border_geometry.h" #include "third_party/blink/renderer/core/paint/scoped_paint_state.h" +#include "third_party/blink/renderer/core/paint/scoped_svg_paint_state.h" #include "third_party/blink/renderer/core/paint/scrollable_area_painter.h" #include "third_party/blink/renderer/core/paint/theme_painter.h" #include "third_party/blink/renderer/core/paint/url_metadata_utils.h" @@ -1655,7 +1656,14 @@ return; } + const LayoutObject* layout_object = child_fragment.GetLayoutObject(); if (child_fragment.IsInlineBox()) { + if (layout_object->IsSVGInline()) { + ScopedSVGPaintState paint_state(*layout_object, paint_info); + NGInlineBoxFragmentPainter(cursor, item, child_fragment) + .Paint(paint_info, paint_offset); + return; + } NGInlineBoxFragmentPainter(cursor, item, child_fragment) .Paint(paint_info, paint_offset); return; @@ -1663,7 +1671,7 @@ // Block-in-inline DCHECK(RuntimeEnabledFeatures::LayoutNGBlockInInlineEnabled()); - DCHECK(!child_fragment.GetLayoutObject()->IsInline()); + DCHECK(!layout_object->IsInline()); PaintInfo paint_info_for_descendants = paint_info.ForDescendants(); paint_info_for_descendants.SetIsInFragmentTraversal(); PaintBlockChild({&child_fragment, item.OffsetInContainerFragment()},
diff --git a/third_party/blink/renderer/core/testing/null_execution_context.h b/third_party/blink/renderer/core/testing/null_execution_context.h index 87546b36..4b6c28b 100644 --- a/third_party/blink/renderer/core/testing/null_execution_context.h +++ b/third_party/blink/renderer/core/testing/null_execution_context.h
@@ -54,6 +54,7 @@ scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner(TaskType) override; void CountUse(mojom::WebFeature) override {} + void CountDeprecation(mojom::WebFeature) override {} const BrowserInterfaceBrokerProxy& GetBrowserInterfaceBroker() const override;
diff --git a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc index 70a2257..d076eb3 100644 --- a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc +++ b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
@@ -252,6 +252,10 @@ ReportingProxy().CountFeature(feature); } +void WorkerOrWorkletGlobalScope::CountDeprecation(WebFeature feature) { + Deprecation::CountDeprecation(this, feature); +} + ResourceLoadScheduler::ThrottleOptionOverride WorkerOrWorkletGlobalScope::GetThrottleOptionOverride() const { return ResourceLoadScheduler::ThrottleOptionOverride::kNone;
diff --git a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h index 9f6948b2..a7e2f9d 100644 --- a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h +++ b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h
@@ -90,6 +90,7 @@ // UseCounter void CountUse(WebFeature feature) final; + void CountDeprecation(WebFeature feature) final; // May return nullptr if this global scope is not threaded (i.e., // WorkletGlobalScope for the main thread) or after Dispose() is called.
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc index 94b3564..54c677d 100644 --- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc +++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc
@@ -74,7 +74,7 @@ const Vector<absl::optional<base::TimeDelta>>& local_times, const Vector<Timing>& timings, const Vector<Timing::NormalizedTiming>& normalized_timings) { - DCHECK(!animators_.at(animation_id)); + DCHECK(!animators_.DeprecatedAtOrEmptyValue(animation_id)); Animator* animator = CreateInstance(name, options, serialized_state, local_times, timings, normalized_timings); if (!animator) @@ -137,7 +137,7 @@ for (const auto& animation : input.added_and_updated_animations) { int id = animation.worklet_animation_id.animation_id; - Animator* animator = animators_.at(id); + Animator* animator = animators_.DeprecatedAtOrEmptyValue(id); // We don't try to create an animator if there isn't any. // This can only happen if constructing an animator instance has failed // e.g., the constructor throws an exception. @@ -150,7 +150,7 @@ for (const auto& animation : input.updated_animations) { int id = animation.worklet_animation_id.animation_id; - Animator* animator = animators_.at(id); + Animator* animator = animators_.DeprecatedAtOrEmptyValue(id); // We don't try to create an animator if there isn't any. if (!animator || !predicate(animator)) continue; @@ -333,7 +333,7 @@ AnimatorDefinition* AnimationWorkletGlobalScope::FindDefinitionForTest( const String& name) { - return animator_definitions_.at(name); + return animator_definitions_.DeprecatedAtOrEmptyValue(name); } } // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc index 073dc53e..0fc71f4 100644 --- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc +++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -194,14 +194,14 @@ PopAndRestore(); } - DCHECK_GE(state_stack_.back()->GetSaveType(), - CanvasRenderingContext2DState::SaveType::kBeginEndLayer); + DCHECK(state_stack_.back()->GetSaveType() == + CanvasRenderingContext2DState::SaveType::kBeginEndLayer); PopAndRestore(); layer_count_--; } void BaseRenderingContext2D::PopAndRestore() { - DCHECK_GE(state_stack_.size(), 1u); + DCHECK_GT(state_stack_.size(), 1u); state_stack_.pop_back(); state_stack_.back()->ClearResolvedFilter();
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h index 6d2d7c4..1860316e 100644 --- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h +++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h
@@ -38,8 +38,9 @@ public: enum ClipListCopyMode { kCopyClipList, kDontCopyClipList }; // SaveType indicates whether the state was pushed to the state stack by Save - // or by BeginLayer. By default, it is set to kSaveRestore. - enum class SaveType { kSaveRestore, kBeginEndLayer }; + // or by BeginLayer. The first state on the state stack, which is created in + // the canvas constructor and not by Save or BeginLayer, has SaveType kIntiial. + enum class SaveType { kSaveRestore, kBeginEndLayer, kInitial }; CanvasRenderingContext2DState(); CanvasRenderingContext2DState(const CanvasRenderingContext2DState&, @@ -307,7 +308,7 @@ ClipList clip_list_; - const SaveType save_type_ = SaveType::kSaveRestore; + const SaveType save_type_ = SaveType::kInitial; DISALLOW_COPY_AND_ASSIGN(CanvasRenderingContext2DState); };
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc index e207fc4b..7f6a296 100644 --- a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc +++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc
@@ -36,6 +36,7 @@ namespace { const int kThumbRadius = 6; +const base::TimeDelta kRenderTimelineInterval = base::TimeDelta::FromSeconds(1); // Only respond to main button of primary pointer(s). bool IsValidPointerEvent(const blink::Event& event) { @@ -82,6 +83,13 @@ void MediaControlTimelineElement::SetPosition(double current_time, bool suppress_aria) { + if (is_live_ && !live_anchor_time_ && current_time != 0) { + live_anchor_time_.emplace(); + live_anchor_time_->clock_time_ = base::TimeTicks::Now(); + live_anchor_time_->media_time_ = MediaElement().currentTime(); + } + + MaybeUpdateTimelineInterval(); setValue(String::Number(current_time)); if (!suppress_aria) @@ -91,8 +99,11 @@ } void MediaControlTimelineElement::SetDuration(double duration) { - double duration_value = std::isfinite(duration) ? duration : 0; - SetFloatingPointAttribute(html_names::kMaxAttr, duration_value); + is_live_ = std::isinf(duration); + double duration_value = duration; + SetFloatingPointAttribute(html_names::kMaxAttr, + is_live_ ? 0.0 : duration_value); + SetFloatingPointAttribute(html_names::kMinAttr, 0.0); RenderBarSegments(); } @@ -109,10 +120,12 @@ if (BeginScrubbingEvent(event)) { Platform::Current()->RecordAction( UserMetricsAction("Media.Controls.ScrubbingBegin")); + is_scrubbing_ = true; GetMediaControls().BeginScrubbing(MediaControlsImpl::IsTouchEvent(&event)); } else if (EndScrubbingEvent(event)) { Platform::Current()->RecordAction( UserMetricsAction("Media.Controls.ScrubbingEnd")); + is_scrubbing_ = false; GetMediaControls().EndScrubbing(); } @@ -163,6 +176,58 @@ event, GetLayoutObject()); } +void MediaControlTimelineElement::OnMediaPlaying() { + if (!is_live_) + return; + + if (render_timeline_timer_.IsRunning()) + render_timeline_timer_.Stop(); +} + +void MediaControlTimelineElement::OnMediaStoppedPlaying() { + if (!is_live_ || is_scrubbing_ || !live_anchor_time_) + return; + + render_timeline_timer_.Start( + FROM_HERE, kRenderTimelineInterval, + WTF::BindRepeating(&MediaControlTimelineElement::UpdateLiveTimeline, + WrapWeakPersistent(this))); +} + +void MediaControlTimelineElement::OnProgress() { + MaybeUpdateTimelineInterval(); + RenderBarSegments(); +} + +void MediaControlTimelineElement::UpdateLiveTimeline() { + MaybeUpdateTimelineInterval(); + RenderBarSegments(); +} + +void MediaControlTimelineElement::MaybeUpdateTimelineInterval() { + if (!is_live_ || !MediaElement().seekable()->length() || !live_anchor_time_) + return; + + int last_seekable = MediaElement().seekable()->length() - 1; + double seekable_start = + MediaElement().seekable()->start(last_seekable, ASSERT_NO_EXCEPTION); + double seekable_end = + MediaElement().seekable()->end(last_seekable, ASSERT_NO_EXCEPTION); + double expected_media_time_now = + live_anchor_time_->media_time_ + + (base::TimeTicks::Now() - live_anchor_time_->clock_time_).InSecondsF(); + + // Cap the current live time in seekable range. + if (expected_media_time_now > seekable_end) { + live_anchor_time_->media_time_ = seekable_end; + live_anchor_time_->clock_time_ = base::TimeTicks::Now(); + expected_media_time_now = seekable_end; + } + + SetFloatingPointAttribute(html_names::kMinAttr, seekable_start); + SetFloatingPointAttribute(html_names::kMaxAttr, expected_media_time_now); +} + void MediaControlTimelineElement::RenderBarSegments() { SetupBarSegments(); @@ -174,6 +239,16 @@ // buffered range containing the current play head. TimeRanges* buffered_time_ranges = MediaElement().buffered(); DCHECK(buffered_time_ranges); + + // Calculate |current_time| and |duration| for live media base on the timeline + // value since timeline's minimum value is not necessarily zero. + if (is_live_) { + current_time = + value().ToDouble() - GetFloatingPointAttribute(html_names::kMinAttr); + duration = GetFloatingPointAttribute(html_names::kMaxAttr) - + GetFloatingPointAttribute(html_names::kMinAttr); + } + if (std::isnan(duration) || std::isinf(duration) || !duration || std::isnan(current_time)) { SetBeforeSegmentPosition(MediaControlSliderElement::Position(0, 0));
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.h b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.h index c40ec8d..f0c62f0 100644 --- a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.h +++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.h
@@ -5,6 +5,9 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_ELEMENTS_MEDIA_CONTROL_TIMELINE_ELEMENT_H_ #define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_ELEMENTS_MEDIA_CONTROL_TIMELINE_ELEMENT_H_ +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/modules/media_controls/elements/media_control_slider_element.h" #include "third_party/blink/renderer/modules/modules_export.h" @@ -28,6 +31,10 @@ void OnMediaKeyboardEvent(Event* event) { DefaultEventHandler(*event); } + void OnMediaPlaying(); + void OnMediaStoppedPlaying(); + void OnProgress(); + void RenderBarSegments(); // Inform the timeline that the Media Controls have been shown or hidden. @@ -40,9 +47,18 @@ const char* GetNameForHistograms() const override; private: + // Struct used to track the current live time. + struct LiveAnchorTime { + base::TimeTicks clock_time_; + double media_time_ = 0; + }; + void DefaultEventHandler(Event&) override; bool KeepEventInNode(const Event&) const override; + void UpdateLiveTimeline(); + void MaybeUpdateTimelineInterval(); + // Checks if we can begin or end a scrubbing event. If the event is a pointer // event then it needs to start and end with valid pointer events. If the // event is a pointer event followed by a touch event then it can only be @@ -55,6 +71,14 @@ bool is_touching_ = false; bool controls_hidden_ = false; + + bool is_scrubbing_ = false; + + bool is_live_ = false; + + absl::optional<LiveAnchorTime> live_anchor_time_; + + base::RepeatingTimer render_timeline_timer_; }; } // namespace blink
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc index a260d500..1b17f93 100644 --- a/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc +++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
@@ -1864,11 +1864,13 @@ void MediaControlsImpl::OnPlaying() { StartHideMediaControlsTimer(); UpdateCSSClassFromState(); + timeline_->OnMediaPlaying(); } void MediaControlsImpl::OnPause() { UpdatePlayState(); UpdateTimeIndicators(); + timeline_->OnMediaStoppedPlaying(); MakeOpaque(); StopHideMediaControlsTimer(); @@ -1993,7 +1995,7 @@ } void MediaControlsImpl::OnLoadingProgress() { - timeline_->RenderBarSegments(); + timeline_->OnProgress(); } void MediaControlsImpl::ComputeWhichControlsFit() { @@ -2193,6 +2195,7 @@ } void MediaControlsImpl::OnWaiting() { + timeline_->OnMediaStoppedPlaying(); UpdateCSSClassFromState(); }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu.cc b/third_party/blink/renderer/modules/webgpu/gpu.cc index ab48663..47cdbed 100644 --- a/third_party/blink/renderer/modules/webgpu/gpu.cc +++ b/third_party/blink/renderer/modules/webgpu/gpu.cc
@@ -203,8 +203,7 @@ auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); ScriptPromise promise = resolver->Promise(); - if (!dawn_control_client_ || dawn_control_client_->IsContextLost() || - !dawn_control_client_->GetContextProviderWeakPtr()) { + if (!dawn_control_client_ || dawn_control_client_->IsContextLost()) { ExecutionContext* execution_context = ExecutionContext::From(script_state); // TODO(natlee@microsoft.com): if GPU process is lost, wait for the GPU
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc b/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc index 4483958..44ea86fe 100644 --- a/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc +++ b/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc
@@ -91,11 +91,12 @@ return features_; } -void GPUAdapter::OnRequestDeviceCallback(ScriptPromiseResolver* resolver, +void GPUAdapter::OnRequestDeviceCallback(ScriptState* script_state, + ScriptPromiseResolver* resolver, const GPUDeviceDescriptor* descriptor, WGPUDevice dawn_device) { if (dawn_device) { - ExecutionContext* execution_context = resolver->GetExecutionContext(); + ExecutionContext* execution_context = ExecutionContext::From(script_state); auto* device = MakeGarbageCollected<GPUDevice>(execution_context, GetDawnControlClient(), this, dawn_device, descriptor); @@ -181,7 +182,8 @@ context_provider->ContextProvider()->WebGPUInterface()->RequestDeviceAsync( adapter_service_id_, requested_device_properties, WTF::Bind(&GPUAdapter::OnRequestDeviceCallback, WrapPersistent(this), - WrapPersistent(resolver), WrapPersistent(descriptor))); + WrapPersistent(script_state), WrapPersistent(resolver), + WrapPersistent(descriptor))); } else { resolver->Reject(MakeGarbageCollected<DOMException>( DOMExceptionCode::kOperationError, "WebGPU context lost"));
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_adapter.h b/third_party/blink/renderer/modules/webgpu/gpu_adapter.h index dce988b..1bdb75d 100644 --- a/third_party/blink/renderer/modules/webgpu/gpu_adapter.h +++ b/third_party/blink/renderer/modules/webgpu/gpu_adapter.h
@@ -52,7 +52,8 @@ const char* message); private: - void OnRequestDeviceCallback(ScriptPromiseResolver* resolver, + void OnRequestDeviceCallback(ScriptState* script_state, + ScriptPromiseResolver* resolver, const GPUDeviceDescriptor* descriptor, WGPUDevice dawn_device); void InitializeFeatureNameList();
diff --git a/third_party/blink/renderer/modules/webtransport/web_transport.cc b/third_party/blink/renderer/modules/webtransport/web_transport.cc index 9873082..37832e8 100644 --- a/third_party/blink/renderer/modules/webtransport/web_transport.cc +++ b/third_party/blink/renderer/modules/webtransport/web_transport.cc
@@ -840,7 +840,7 @@ bool fin_received) { DVLOG(1) << "WebTransport::OnIncomingStreamClosed(" << stream_id << ", " << fin_received << ") this=" << this; - WebTransportStream* stream = stream_map_.at(stream_id); + WebTransportStream* stream = stream_map_.DeprecatedAtOrEmptyValue(stream_id); // |stream| can be unset because of races between different ways of closing // |bidirectional streams. if (stream) {
diff --git a/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc b/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc index 034ded0..01cb98a 100644 --- a/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc +++ b/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc
@@ -86,6 +86,8 @@ void HRTFDatabaseLoader::LoadAsynchronously() { DCHECK(IsMainThread()); + MutexLocker locker(lock_); + // m_hrtfDatabase and m_thread should both be unset because this should be a // new HRTFDatabaseLoader object that was just created by // createAndLoadAsynchronouslyIfNecessary and because we haven't started @@ -122,6 +124,10 @@ } void HRTFDatabaseLoader::WaitForLoaderThreadCompletion() { + // We can lock this because this is called from either the main thread or + // the offline audio rendering thread. + MutexLocker locker(lock_); + if (!thread_) return;
diff --git a/third_party/blink/renderer/platform/audio/hrtf_database_loader.h b/third_party/blink/renderer/platform/audio/hrtf_database_loader.h index 3ce476f..a94997b4 100644 --- a/third_party/blink/renderer/platform/audio/hrtf_database_loader.h +++ b/third_party/blink/renderer/platform/audio/hrtf_database_loader.h
@@ -64,8 +64,8 @@ // must be called from the audio thread. bool IsLoaded() { return Database(); } - // waitForLoaderThreadCompletion() may be called more than once and is - // thread-safe. + // May be called from both main and audio thread, and also can be called more + // than once. void WaitForLoaderThreadCompletion(); // Returns the database or nullptr if the database doesn't yet exist. Must @@ -87,11 +87,10 @@ void LoadTask(); void CleanupTask(base::WaitableEvent*); - // Holding a m_lock is required when accessing m_hrtfDatabase since we access - // it from multiple threads. + // |lock_| MUST be held when accessing |hrtf_database_| or |thread_| because + // it can be accessed by multiple threads (e.g multiple AudioContexts). Mutex lock_; std::unique_ptr<HRTFDatabase> hrtf_database_; - std::unique_ptr<Thread> thread_; float database_sample_rate_;
diff --git a/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc b/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc index 1f6e80bb0f..d38eaa4 100644 --- a/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc +++ b/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc
@@ -226,7 +226,8 @@ String DOMWrapperWorld::NonMainWorldHumanReadableName() const { DCHECK(!IsMainWorld()); - return IsolatedWorldHumanReadableNames().at(GetWorldId()); + return IsolatedWorldHumanReadableNames().DeprecatedAtOrEmptyValue( + GetWorldId()); } void DOMWrapperWorld::SetNonMainWorldHumanReadableName(
diff --git a/third_party/blink/renderer/platform/bindings/v8_global_value_map.h b/third_party/blink/renderer/platform/bindings/v8_global_value_map.h index ac7c7a2..ee71262 100644 --- a/third_party/blink/renderer/platform/bindings/v8_global_value_map.h +++ b/third_party/blink/renderer/platform/bindings/v8_global_value_map.h
@@ -48,7 +48,7 @@ return old_value; } static v8::PersistentContainerValue Get(const Impl* impl, KeyType key) { - return impl->at(key); + return impl->DeprecatedAtOrEmptyValue(key); } static v8::PersistentContainerValue Remove(Impl* impl, KeyType key) {
diff --git a/third_party/blink/renderer/platform/bindings/v8_per_context_data.cc b/third_party/blink/renderer/platform/bindings/v8_per_context_data.cc index 8d92ce43..e747045 100644 --- a/third_party/blink/renderer/platform/bindings/v8_per_context_data.cc +++ b/third_party/blink/renderer/platform/bindings/v8_per_context_data.cc
@@ -171,7 +171,7 @@ } V8PerContextData::Data* V8PerContextData::GetData(const char* key) { - return data_map_.at(key); + return data_map_.DeprecatedAtOrEmptyValue(key); } } // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc index c699c8a..65a72b04 100644 --- a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc +++ b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc
@@ -5,7 +5,7 @@ #include "third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h" #include "base/check.h" -#include "gpu/command_buffer/client/webgpu_interface.h" +#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_resource_provider_cache.h" #include "third_party/blink/renderer/platform/wtf/functional.h" namespace blink { @@ -18,9 +18,8 @@ base::MakeRefCounted<DawnControlClientHolder>(std::move(context_provider), std::move(task_runner)); dawn_control_client_holder->context_provider_->ContextProvider() - ->SetLostContextCallback( - WTF::BindRepeating(&DawnControlClientHolder::SetContextLost, - dawn_control_client_holder)); + ->SetLostContextCallback(WTF::BindRepeating( + &DawnControlClientHolder::Destroy, dawn_control_client_holder)); return dawn_control_client_holder; } @@ -29,16 +28,35 @@ scoped_refptr<base::SingleThreadTaskRunner> task_runner) : context_provider_(std::make_unique<WebGraphicsContext3DProviderWrapper>( std::move(context_provider))), - procs_( - context_provider_->ContextProvider()->WebGPUInterface()->GetProcs()), + task_runner_(task_runner), + api_channel_(context_provider_->ContextProvider() + ->WebGPUInterface() + ->GetAPIChannel()), + procs_(api_channel_->GetProcs()), recyclable_resource_cache_(GetContextProviderWeakPtr(), task_runner) {} +DawnControlClientHolder::~DawnControlClientHolder() = default; + void DawnControlClientHolder::Destroy() { - SetContextLost(); + api_channel_->Disconnect(); + + // Destroy the WebGPU context. + // This ensures that GPU resources are eagerly reclaimed. + // Because we have disconnected the wire client, any JavaScript which uses + // WebGPU will do nothing. if (context_provider_) { - context_provider_->ContextProvider() - ->WebGPUInterface() - ->DisconnectContextAndDestroyServer(); + // If the context provider is destroyed during a real lost context event, it + // causes the CommandBufferProxy that the context provider owns, which is + // what issued the lost context event in the first place, to be destroyed + // before the event is done being handled. This causes a crash when an + // outstanding AutoLock goes out of scope. To avoid this, we create a no-op + // task to hold a reference to the context provider until this function is + // done executing, and drop it after. + task_runner_->PostTask( + FROM_HERE, + base::BindOnce([](std::unique_ptr<WebGraphicsContext3DProviderWrapper> + context_provider) {}, + std::move(context_provider_))); } } @@ -50,12 +68,8 @@ return context_provider_->GetWeakPtr(); } -void DawnControlClientHolder::SetContextLost() { - lost_ = true; -} - bool DawnControlClientHolder::IsContextLost() const { - return lost_; + return !context_provider_; } std::unique_ptr<RecyclableCanvasResource>
diff --git a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h index e30657c0..fd58ce9 100644 --- a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h +++ b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h
@@ -8,24 +8,26 @@ #include <dawn/dawn_proc_table.h> #include <dawn/webgpu.h> +#include "gpu/command_buffer/client/webgpu_interface.h" #include "third_party/blink/renderer/platform/graphics/gpu/webgpu_resource_provider_cache.h" #include "third_party/blink/renderer/platform/graphics/web_graphics_context_3d_provider_wrapper.h" #include "third_party/blink/renderer/platform/platform_export.h" #include "third_party/blink/renderer/platform/wtf/ref_counted.h" -namespace gpu { -namespace webgpu { +namespace base { -class WebGPUInterface; +class SingleThreadTaskRunner; -} // namespace webgpu -} // namespace gpu +} // namespace base namespace blink { -// This class holds the WebGPUInterface and a |destroyed_| flag. -// DawnControlClientHolder::Destroy() should be called to destroy the backing -// WebGPUInterface. +// This class holds the WebGraphicsContext3DProviderWrapper and a strong +// reference to the WebGPU APIChannel. +// DawnControlClientHolder::Destroy() should be called to destroy the +// context which will free all command buffer and GPU resources. As long +// as the reference to the APIChannel is held, calling WebGPU procs is +// valid. class PLATFORM_EXPORT DawnControlClientHolder : public RefCounted<DawnControlClientHolder> { public: @@ -45,7 +47,6 @@ base::WeakPtr<WebGraphicsContext3DProviderWrapper> GetContextProviderWeakPtr() const; const DawnProcTable& GetProcs() const { return procs_; } - void SetContextLost(); bool IsContextLost() const; std::unique_ptr<RecyclableCanvasResource> GetOrCreateCanvasResource( const IntSize& size, @@ -54,11 +55,12 @@ private: friend class RefCounted<DawnControlClientHolder>; - ~DawnControlClientHolder() = default; + ~DawnControlClientHolder(); std::unique_ptr<WebGraphicsContext3DProviderWrapper> context_provider_; + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + scoped_refptr<gpu::webgpu::APIChannel> api_channel_; DawnProcTable procs_; - bool lost_ = false; WebGPURecyclableResourceCache recyclable_resource_cache_; };
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler_test.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler_test.cc index 15ff68fa..afcac98 100644 --- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler_test.cc +++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler_test.cc
@@ -59,13 +59,11 @@ class MockWebGPUInterface : public gpu::webgpu::WebGPUInterfaceStub { public: MockWebGPUInterface() { - procs_ = {}; - // WebGPU functions the tests will call. No-op them since we don't have a // real WebGPU device. - procs_.deviceReference = [](WGPUDevice) {}; - procs_.deviceRelease = [](WGPUDevice) {}; - procs_.textureRelease = [](WGPUTexture) {}; + procs()->deviceReference = [](WGPUDevice) {}; + procs()->deviceRelease = [](WGPUDevice) {}; + procs()->textureRelease = [](WGPUTexture) {}; } MOCK_METHOD(gpu::webgpu::ReservedTexture, @@ -82,11 +80,6 @@ MOCK_METHOD(void, DissociateMailbox, (GLuint texture_id, GLuint texture_generation)); - - const DawnProcTable& GetProcs() const override { return procs_; } - - private: - DawnProcTable procs_; }; // The six reference pixels are: red, green, blue, white, black.
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc index 35039d1..5980266 100644 --- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc +++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
@@ -21,12 +21,10 @@ class MockWebGPUInterface : public gpu::webgpu::WebGPUInterfaceStub { public: MockWebGPUInterface() { - procs_ = {}; - // WebGPU functions the tests will call. No-op them since we don't have a // real WebGPU device. - procs_.deviceReference = [](WGPUDevice) {}; - procs_.deviceRelease = [](WGPUDevice) {}; + procs()->deviceReference = [](WGPUDevice) {}; + procs()->deviceRelease = [](WGPUDevice) {}; } MOCK_METHOD1(ReserveTexture, gpu::webgpu::ReservedTexture(WGPUDevice device)); @@ -51,13 +49,10 @@ memcpy(&most_recent_waited_token, sync_token_data, sizeof(gpu::SyncToken)); } - const DawnProcTable& GetProcs() const override { return procs_; } - gpu::SyncToken most_recent_generated_token; gpu::SyncToken most_recent_waited_token; private: - DawnProcTable procs_; uint64_t token_id_ = 42; };
diff --git a/third_party/blink/renderer/platform/instrumentation/use_counter.h b/third_party/blink/renderer/platform/instrumentation/use_counter.h index e93eac92..bd1ffcb 100644 --- a/third_party/blink/renderer/platform/instrumentation/use_counter.h +++ b/third_party/blink/renderer/platform/instrumentation/use_counter.h
@@ -50,6 +50,10 @@ // Counts a use of the given feature. Repeated calls are ignored. virtual void CountUse(mojom::WebFeature feature) = 0; + + // Counts a use of the given feature which is being deprecated. Repeated + // calls are ignored. + virtual void CountDeprecation(mojom::WebFeature feature) = 0; }; } // namespace blink
diff --git a/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc b/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc index f4db817..b0ca0bd5 100644 --- a/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc +++ b/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc
@@ -30,6 +30,7 @@ } MOCK_METHOD1(CountUse, void(mojom::WebFeature)); + MOCK_METHOD1(CountDeprecation, void(mojom::WebFeature)); }; class MockConsoleLogger : public GarbageCollected<MockConsoleLogger>,
diff --git a/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h b/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h index a2478953..20e1fd0 100644 --- a/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h +++ b/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h
@@ -24,6 +24,11 @@ use_counter_->CountUse(feature); } } + void CountDeprecation(mojom::WebFeature feature) override { + if (use_counter_) { + use_counter_->CountDeprecation(feature); + } + } void Trace(Visitor* visitor) const override { visitor->Trace(use_counter_); } void Detach() { use_counter_ = nullptr; }
diff --git a/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc b/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc index 7ceb5eb..5a4c5d9 100644 --- a/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc +++ b/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc
@@ -170,7 +170,8 @@ TRACE_EVENT1("blink", "MemoryCache::evict", "resource", resource->Url().GetString().Utf8()); - ResourceMap* resources = resource_maps_.at(resource->CacheIdentifier()); + ResourceMap* resources = + resource_maps_.DeprecatedAtOrEmptyValue(resource->CacheIdentifier()); if (!resources) return; @@ -196,11 +197,12 @@ bool MemoryCache::Contains(const Resource* resource) const { if (!resource || resource->Url().IsEmpty()) return false; - const ResourceMap* resources = resource_maps_.at(resource->CacheIdentifier()); + const ResourceMap* resources = + resource_maps_.DeprecatedAtOrEmptyValue(resource->CacheIdentifier()); if (!resources) return false; KURL url = RemoveFragmentIdentifierIfNeeded(resource->Url()); - MemoryCacheEntry* entry = resources->at(url); + MemoryCacheEntry* entry = resources->DeprecatedAtOrEmptyValue(url); return entry && resource == entry->GetResource(); } @@ -214,11 +216,12 @@ if (!resource_url.IsValid() || resource_url.IsNull()) return nullptr; DCHECK(!cache_identifier.IsNull()); - const ResourceMap* resources = resource_maps_.at(cache_identifier); + const ResourceMap* resources = + resource_maps_.DeprecatedAtOrEmptyValue(cache_identifier); if (!resources) return nullptr; - MemoryCacheEntry* entry = - resources->at(RemoveFragmentIdentifierIfNeeded(resource_url)); + MemoryCacheEntry* entry = resources->DeprecatedAtOrEmptyValue( + RemoveFragmentIdentifierIfNeeded(resource_url)); if (!entry) return nullptr; return entry->GetResource();
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc index 8fc72ba..7eba67fe 100644 --- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc +++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -538,7 +538,8 @@ if (resource_url.IsEmpty()) return nullptr; KURL url = MemoryCache::RemoveFragmentIdentifierIfNeeded(resource_url); - const WeakMember<Resource>& resource = cached_resources_map_.at(url); + const WeakMember<Resource>& resource = + cached_resources_map_.DeprecatedAtOrEmptyValue(url); return resource.Get(); }
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc index db1aa6b..0916745 100644 --- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc +++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -739,7 +739,7 @@ DCHECK(!passed_redirect_response.IsNull()); if (passed_redirect_response.HasAuthorizationCoveredByWildcardOnPreflight()) { - fetcher_->GetUseCounter().CountUse( + fetcher_->GetUseCounter().CountDeprecation( mojom::WebFeature::kAuthorizationCoveredByWildcard); } @@ -922,7 +922,7 @@ const ResourceRequestHead& request = resource_->GetResourceRequest(); if (response.HasAuthorizationCoveredByWildcardOnPreflight()) { - fetcher_->GetUseCounter().CountUse( + fetcher_->GetUseCounter().CountDeprecation( mojom::WebFeature::kAuthorizationCoveredByWildcard); }
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc index 23d4055..0f5c331 100644 --- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc +++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
@@ -224,6 +224,16 @@ spatial_layers, media::VideoEncodeAccelerator::Config::InterLayerPredMode* inter_layer_pred) { + if (codec_settings.codecType == webrtc::kVideoCodecH264 && + codec_settings.H264().numberOfTemporalLayers > 1 && + !RTCVideoEncoder::H264HwSupportForTemporalLayers()) { + DVLOG(1) << "H264 temporal layers not yet supported by HW codecs, but use" + << " HW codecs and leave the fallback decision to a webrtc client" + << " by seeing metadata in webrtc::CodecSpecificInfo"; + + return true; + } + if (codec_settings.codecType == webrtc::kVideoCodecVP8 && codec_settings.mode == webrtc::VideoCodecMode::kScreensharing && codec_settings.VP8().numberOfTemporalLayers > 1) { @@ -249,6 +259,22 @@ // We fill SpatialLayer only in temporal layer or spatial layer encoding. switch (codec_settings.codecType) { + case webrtc::kVideoCodecH264: + if (codec_settings.H264().numberOfTemporalLayers > 1) { + // Though we don't support H264 SVC. We allocate 1 element in + // spatial_layers for temporal layer encoding. + spatial_layers->resize(1u); + auto& sl = (*spatial_layers)[0]; + sl.width = codec_settings.width; + sl.height = codec_settings.height; + if (!ConvertKbpsToBps(codec_settings.startBitrate, &sl.bitrate_bps)) + return false; + sl.framerate = codec_settings.maxFramerate; + sl.max_qp = base::saturated_cast<uint8_t>(codec_settings.qpMax); + sl.num_of_temporal_layers = base::saturated_cast<uint8_t>( + codec_settings.H264().numberOfTemporalLayers); + } + break; case webrtc::kVideoCodecVP8: if (codec_settings.VP8().numberOfTemporalLayers > 1) { // Though there is no SVC in VP8 spec. We allocate 1 element in @@ -993,6 +1019,18 @@ webrtc::CodecSpecificInfo info; info.codecType = video_codec_type_; switch (video_codec_type_) { + case webrtc::kVideoCodecH264: { + webrtc::CodecSpecificInfoH264& h264 = info.codecSpecific.H264; + h264.packetization_mode = webrtc::H264PacketizationMode::NonInterleaved; + h264.idr_frame = metadata.key_frame; + if (metadata.h264) { + h264.temporal_idx = metadata.h264->temporal_idx; + h264.base_layer_sync = metadata.h264->layer_sync; + } else { + h264.temporal_idx = webrtc::kNoTemporalIdx; + h264.base_layer_sync = false; + } + } break; case webrtc::kVideoCodecVP8: info.codecSpecific.VP8.keyIdx = -1; break; @@ -1599,10 +1637,13 @@ } // static +bool RTCVideoEncoder::H264HwSupportForTemporalLayers() { + // Temporal layers are not supported by hardware encoders. + return false; +} + +// static bool RTCVideoEncoder::Vp9HwSupportForSpatialLayers() { - // TODO(crbug.com/1217919): Query spatial scalability support for the HW - // encoder instead of checking OS. - // Underlying encoder will check the spatial SVC encoding capability on CrOS. #if defined(ARCH_CPU_X86_FAMILY) && BUILDFLAG(IS_CHROMEOS_ASH) return base::FeatureList::IsEnabled(media::kVaapiVp9kSVCHWEncoding); #endif
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h index f3c86392..1ff816a8 100644 --- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h +++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h
@@ -64,6 +64,8 @@ const webrtc::VideoEncoder::RateControlParameters& parameters) override; EncoderInfo GetEncoderInfo() const override; + // Returns true if there's H264 HW support for temporal layers. + static bool H264HwSupportForTemporalLayers(); // Returns true if there's VP9 HW support for spatial layers. static bool Vp9HwSupportForSpatialLayers();
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc index 6fc44c91..eb4a7f3 100644 --- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc +++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
@@ -111,14 +111,14 @@ EXPECT_CALL(*mock_gpu_factories_.get(), GetTaskRunner()) .WillRepeatedly(Return(encoder_thread_.task_runner())); - mock_vea_ = ExpectCreateInitAndDestroyVEA(); } void TearDown() override { DVLOG(3) << __func__; EXPECT_TRUE(encoder_thread_.IsRunning()); RunUntilIdle(); - rtc_encoder_->Release(); + if (rtc_encoder_) + rtc_encoder_->Release(); RunUntilIdle(); encoder_thread_.Stop(); } @@ -148,6 +148,8 @@ ADD_FAILURE() << "Unexpected codec type: " << codec_type; media_profile = media::VIDEO_CODEC_PROFILE_UNKNOWN; } + + mock_vea_ = ExpectCreateInitAndDestroyVEA(); rtc_encoder_ = std::make_unique<RTCVideoEncoder>(media_profile, false, mock_gpu_factories_.get()); } @@ -180,8 +182,8 @@ return codec; } - webrtc::VideoCodec GetSVCLayerCodec(size_t num_spatial_layers) { - const webrtc::VideoCodecType codec_type = webrtc::kVideoCodecVP9; + webrtc::VideoCodec GetSVCLayerCodec(webrtc::VideoCodecType codec_type, + size_t num_spatial_layers) { webrtc::VideoCodec codec{}; codec.codecType = codec_type; codec.width = kInputFrameWidth; @@ -194,23 +196,34 @@ codec.qpMax = 30; codec.numberOfSimulcastStreams = 1; codec.mode = webrtc::VideoCodecMode::kRealtimeVideo; - webrtc::VideoCodecVP9& vp9 = *codec.VP9(); - vp9.numberOfTemporalLayers = 3; - vp9.numberOfSpatialLayers = num_spatial_layers; - num_spatial_layers_ = num_spatial_layers; - constexpr int kDenom[] = {4, 2, 1}; - for (size_t sid = 0; sid < num_spatial_layers; ++sid) { - webrtc::SpatialLayer& sl = codec.spatialLayers[sid]; - const int denom = kDenom[sid]; - sl.width = kInputFrameWidth / denom; - sl.height = kInputFrameHeight / denom; - sl.maxFramerate = 24; - sl.numberOfTemporalLayers = vp9.numberOfTemporalLayers; - sl.targetBitrate = kStartBitrate / denom; - sl.maxBitrate = sl.targetBitrate / denom; - sl.minBitrate = sl.targetBitrate / denom; - sl.qpMax = 30; - sl.active = true; + + switch (codec_type) { + case webrtc::kVideoCodecH264: { + webrtc::VideoCodecH264& h264 = *codec.H264(); + h264.numberOfTemporalLayers = 2; + } break; + case webrtc::kVideoCodecVP9: { + webrtc::VideoCodecVP9& vp9 = *codec.VP9(); + vp9.numberOfTemporalLayers = 3; + vp9.numberOfSpatialLayers = num_spatial_layers; + num_spatial_layers_ = num_spatial_layers; + constexpr int kDenom[] = {4, 2, 1}; + for (size_t sid = 0; sid < num_spatial_layers; ++sid) { + webrtc::SpatialLayer& sl = codec.spatialLayers[sid]; + const int denom = kDenom[sid]; + sl.width = kInputFrameWidth / denom; + sl.height = kInputFrameHeight / denom; + sl.maxFramerate = 24; + sl.numberOfTemporalLayers = vp9.numberOfTemporalLayers; + sl.targetBitrate = kStartBitrate / denom; + sl.maxBitrate = sl.targetBitrate / denom; + sl.minBitrate = sl.targetBitrate / denom; + sl.qpMax = 30; + sl.active = true; + } + } break; + default: + NOTREACHED(); } return codec; } @@ -349,18 +362,25 @@ rtc_encoder_->InitEncode(&codec, kVideoEncoderSettings)); } -INSTANTIATE_TEST_SUITE_P(CodecProfiles, - RTCVideoEncoderTest, - Values(webrtc::kVideoCodecVP8, - webrtc::kVideoCodecH264)); - -TEST_F(RTCVideoEncoderTest, CreateAndInitSucceedsForTemporalLayer) { - webrtc::VideoCodec tl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/1); +TEST_P(RTCVideoEncoderTest, CreateAndInitSucceedsForTemporalLayer) { + const webrtc::VideoCodecType codec_type = GetParam(); + if (codec_type == webrtc::kVideoCodecVP8) { + GTEST_SKIP() << "VP8 temporal layer encoding is not supported"; + return; + } + webrtc::VideoCodec tl_codec = GetSVCLayerCodec(codec_type, + /*num_spatial_layers=*/1); CreateEncoder(tl_codec.codecType); EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&tl_codec, kVideoEncoderSettings)); } +INSTANTIATE_TEST_SUITE_P(CodecProfiles, + RTCVideoEncoderTest, + Values(webrtc::kVideoCodecH264, + webrtc::kVideoCodecVP8, + webrtc::kVideoCodecVP9)); + // Checks that WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE is returned when there is // platform error. TEST_F(RTCVideoEncoderTest, SoftwareFallbackAfterError) { @@ -477,8 +497,9 @@ rtc_encoder_->Encode(rtc_frame, &frame_types)); } -TEST_F(RTCVideoEncoderTest, EncodeTemporalLayer) { - webrtc::VideoCodec tl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/1); +TEST_F(RTCVideoEncoderTest, EncodeVP9TemporalLayer) { + webrtc::VideoCodec tl_codec = GetSVCLayerCodec(webrtc::kVideoCodecVP9, + /*num_spatial_layers=*/1); CreateEncoder(tl_codec.codecType); EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&tl_codec, kVideoEncoderSettings)); @@ -512,7 +533,8 @@ // http://crbug.com/1226875 TEST_F(RTCVideoEncoderTest, EncodeSpatialLayer) { - webrtc::VideoCodec sl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/3); + webrtc::VideoCodec sl_codec = GetSVCLayerCodec(webrtc::kVideoCodecVP9, + /*num_spatial_layers=*/3); CreateEncoder(sl_codec.codecType); EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&sl_codec, kVideoEncoderSettings)); @@ -540,8 +562,9 @@ } } -TEST_F(RTCVideoEncoderTest, CreateAndInitThreeLayerSvc) { - webrtc::VideoCodec tl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/3); +TEST_F(RTCVideoEncoderTest, CreateAndInitVP9ThreeLayerSvc) { + webrtc::VideoCodec tl_codec = GetSVCLayerCodec(webrtc::kVideoCodecVP9, + /*num_spatial_layers=*/3); CreateEncoder(tl_codec.codecType); EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, @@ -558,8 +581,9 @@ Field(&SpatialLayer::height, kInputFrameHeight))))); } -TEST_F(RTCVideoEncoderTest, CreateAndInitSvcSinglecast) { - webrtc::VideoCodec tl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/3); +TEST_F(RTCVideoEncoderTest, CreateAndInitVP9SvcSinglecast) { + webrtc::VideoCodec tl_codec = GetSVCLayerCodec(webrtc::kVideoCodecVP9, + /*num_spatial_layers=*/3); tl_codec.spatialLayers[1].active = false; tl_codec.spatialLayers[2].active = false; CreateEncoder(tl_codec.codecType); @@ -573,8 +597,10 @@ Field(&SpatialLayer::height, kInputFrameHeight / 4))))); } -TEST_F(RTCVideoEncoderTest, CreateAndInitSvcSinglecastWithoutTemporalLayers) { - webrtc::VideoCodec tl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/3); +TEST_F(RTCVideoEncoderTest, + CreateAndInitVP9SvcSinglecastWithoutTemporalLayers) { + webrtc::VideoCodec tl_codec = GetSVCLayerCodec(webrtc::kVideoCodecVP9, + /*num_spatial_layers=*/3); tl_codec.spatialLayers[1].active = false; tl_codec.spatialLayers[2].active = false; tl_codec.spatialLayers[0].numberOfTemporalLayers = 1;
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-features=BackForwardCache b/third_party/blink/web_tests/FlagExpectations/enable-features=BackForwardCache index 78465d4..e60d01b 100644 --- a/third_party/blink/web_tests/FlagExpectations/enable-features=BackForwardCache +++ b/third_party/blink/web_tests/FlagExpectations/enable-features=BackForwardCache
@@ -2,9 +2,6 @@ # tags: [ Release Debug ] # results: [ Timeout Crash Pass Failure Slow Skip ] -# TODO(peria): Make these tests work with same-site back-forward cache. -crbug.com/1155125 http/tests/history/replacestate-post-to-get-2.html [ Timeout ] - # TODO(peria): Make these tests work with same-site browsing instance swap. crbug.com/1136383 http/tests/inspector-protocol/network/interception-download.js [ Failure ] crbug.com/1136383 virtual/sxg-subresource-disabled/http/tests/loading/sxg/sxg-subresource-origin-trial.https.html [ Failure ]
diff --git a/third_party/blink/web_tests/OWNERS b/third_party/blink/web_tests/OWNERS index 72e8ffc..3dfdc72 100644 --- a/third_party/blink/web_tests/OWNERS +++ b/third_party/blink/web_tests/OWNERS
@@ -1 +1,4 @@ * + +per-file VirtualTestSuites=set noparent +per-file VirtualTestSuites=file://third_party/blink/web_tests/VIRTUAL_OWNERS
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index b839ed5..dfa4547 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -2491,13 +2491,17 @@ crbug.com/27659 external/wpt/css/css-ruby/block-ruby-001.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/block-ruby-002.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/block-ruby-005.html [ Failure ] +crbug.com/27659 external/wpt/css/css-ruby/empty-ruby-base-container.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/empty-ruby-text-container-float.html [ Failure ] +crbug.com/27659 external/wpt/css/css-ruby/improperly-contained-annotation-001.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/root-block-ruby.xhtml [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/root-ruby.xhtml [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-align-001.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-align-001a.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-align-002.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-align-002a.html [ Failure ] +crbug.com/27659 external/wpt/css/css-ruby/ruby-base-container-abs.html [ Failure ] +crbug.com/27659 external/wpt/css/css-ruby/ruby-base-container-float.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-bidi-002.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-bidi-003.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-box-generation-001.html [ Failure ] @@ -2509,6 +2513,7 @@ crbug.com/27659 external/wpt/css/css-ruby/ruby-float-handling-001.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-intrinsic-isize-001.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-intrinsic-isize-002.html [ Failure ] +crbug.com/27659 external/wpt/css/css-ruby/ruby-intrinsic-isize-003.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-justification-002.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-lang-specific-style-001.html [ Failure ] crbug.com/27659 external/wpt/css/css-ruby/ruby-line-break-suppression-001.html [ Failure ] @@ -2620,12 +2625,8 @@ crbug.com/626703 external/wpt/service-workers/service-worker/worker-interception.https.html [ Failure ] # ====== New tests from wpt-importer added here ====== -crbug.com/626703 external/wpt/css/css-ruby/improperly-contained-annotation-001.html [ Failure ] crbug.com/626703 [ Mac10.14 ] virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/valid-image-after-load.https.html [ Failure ] -crbug.com/626703 external/wpt/css/css-ruby/ruby-base-container-abs.html [ Failure ] -crbug.com/626703 external/wpt/css/css-ruby/empty-ruby-base-container.html [ Failure ] crbug.com/626703 [ Mac11.0 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-local-time-null-2.https.html [ Failure ] -crbug.com/626703 external/wpt/css/css-ruby/ruby-base-container-float.html [ Failure ] crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/simulcast/getStats.https.html [ Timeout ] crbug.com/626703 [ Mac10.15 ] external/wpt/html/cross-origin-opener-policy/historical/coep-navigate-popup-unsafe-inherit.https.html [ Timeout ] crbug.com/626703 [ Mac10.14 ] external/wpt/html/cross-origin-opener-policy/historical/coep-navigate-popup-unsafe-inherit.https.html [ Timeout ] @@ -3143,7 +3144,6 @@ crbug.com/626703 external/wpt/websockets/stream/tentative/close.any.html?wpt_flags=h2 [ Failure Timeout ] crbug.com/626703 external/wpt/websockets/stream/tentative/close.any.sharedworker.html?wpt_flags=h2 [ Failure Timeout ] crbug.com/626703 external/wpt/websockets/stream/tentative/close.any.serviceworker.html?wpt_flags=h2 [ Failure Timeout ] -crbug.com/626703 external/wpt/css/css-ruby/ruby-intrinsic-isize-003.html [ Failure ] crbug.com/626703 [ Mac10.13 ] external/wpt/selection/contenteditable/modifying-selection-with-primary-mouse-button.tentative.html [ Crash Failure ] crbug.com/626703 [ Mac10.15 ] external/wpt/streams/readable-streams/tee.any.html [ Crash Failure ] crbug.com/626703 [ Mac10.15 ] external/wpt/streams/readable-streams/tee.any.worker.html [ Failure ] @@ -4130,10 +4130,10 @@ crbug.com/1179585 virtual/layout_ng_svg_text/svg/custom/empty-mask.svg [ Failure ] crbug.com/1179585 virtual/layout_ng_svg_text/svg/custom/text-match-highlight.html [ Failure ] crbug.com/1179585 virtual/layout_ng_svg_text/svg/custom/visibility-collapse.html [ Failure ] +crbug.com/1179585 virtual/layout_ng_svg_text/svg/dynamic-updates/SVGTextElement-dom-textLength-attr.html [ Failure ] +crbug.com/1179585 virtual/layout_ng_svg_text/svg/dynamic-updates/SVGTextElement-svgdom-textLength-prop.html [ Failure ] crbug.com/1179585 virtual/layout_ng_svg_text/svg/dynamic-updates/SVGTextElement-dom-lengthAdjust-attr.html [ Failure ] crbug.com/1179585 virtual/layout_ng_svg_text/svg/dynamic-updates/SVGTextElement-svgdom-lengthAdjust-prop.html [ Failure ] -crbug.com/1179585 virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text.svg [ Failure ] -crbug.com/1179585 virtual/layout_ng_svg_text/svg/filters/filter-on-tspan.svg [ Failure ] crbug.com/1179585 virtual/layout_ng_svg_text/svg/stroke/non-scaling-stroke-text-decoration.html [ Failure ] crbug.com/1179585 virtual/layout_ng_svg_text/svg/stroke/non-scaling-stroke-text-decoration-dashed.html [ Failure ] crbug.com/1179585 virtual/layout_ng_svg_text/svg/text/bbox-with-glyph-overflow.html [ Failure ] @@ -6293,7 +6293,6 @@ # Sheriff 2018-01-25 crbug.com/893869 css3/masking/mask-repeat-space-padding.html [ Failure Pass ] -crbug.com/1155125 [ Linux ] http/tests/history/replacestate-post-to-get-2.html [ Pass Timeout ] # V8 roll crbug.com/961059 fast/workers/worker-shared-asm-buffer.html [ Skip ]
diff --git a/third_party/blink/web_tests/VIRTUAL_OWNERS b/third_party/blink/web_tests/VIRTUAL_OWNERS new file mode 100644 index 0000000..38edf3e --- /dev/null +++ b/third_party/blink/web_tests/VIRTUAL_OWNERS
@@ -0,0 +1,21 @@ +# These owners approve changes to VirtualTestSuites. +# +# The following things should be checked whether they are justified in the +# CL description: +# - If the CL adds virtual tests (by adding new virtual suites or new +# directories to existing virtual suties), how many tests the CL adds. +# - If the number of new tests is big (e.g. >=1000), +# - Whether the new tests can be skipped on some platforms / OS versions. +# - Whether the new directories are essential for test coverage. +# - Whether other alternatives [1] are more suitable than virtual suites. +# - How much more time of the swarm jobs for the blink_web_tests step on +# bots will take. +# +# [1] https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_tests.md#testing-runtime-flags + +ikilpatrick@chromium.org +kojii@chromium.org +masonf@chromium.org +pdr@chromium.org +tkent@chromium.org +wangxianzhu@chromium.org
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites index 0106a5a..e742a6d 100644 --- a/third_party/blink/web_tests/VirtualTestSuites +++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1161,3 +1161,4 @@ "args": ["--enable-blink-features=VirtualKeyboard"] } ] +
diff --git a/third_party/blink/web_tests/WebGPUExpectations b/third_party/blink/web_tests/WebGPUExpectations index caa3d789..20cf373 100644 --- a/third_party/blink/web_tests/WebGPUExpectations +++ b/third_party/blink/web_tests/WebGPUExpectations
@@ -131,9 +131,6 @@ crbug.com/1215024 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createComputePipeline:enrty_point_name_must_match:stageEntryPoint="main%5Cu0000";* [ Failure ] crbug.com/1215024 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createComputePipeline:enrty_point_name_must_match:stageEntryPoint="main%5Cu0000a";* [ Failure ] -# Crash when creating the bindgroup. Most likely because we are using an invalid VkImageView -crbug.com/dawn/1043 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createBindGroup:texture,resource_state:* [ Crash Failure ] - # Flaky crash on shutdown crbug.com/1236132 wpt_internal/webgpu/cts.html?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:onscreenCanvas,uploadToWebGL:* [ RetryOnFailure Crash ] @@ -266,18 +263,6 @@ # Nvidia only, worker only, very flaky [ Linux ] wpt_internal/webgpu/cts.html?worker=1&q=webgpu:api,operation,render_pass,storeop2:* [ Failure ] -# Vulkan drivers incorrectly interpret shader modules with multiple entrypoints. -crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:buffers_with_varying_step_mode:* [ Crash Failure ] -crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:non_zero_array_stride_and_attribute_offset:* [ Crash Failure ] -crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:setVertexBuffer_offset_and_attribute_offset:* [ Crash Failure ] -crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:vertex_format_to_shader_format_conversion:* [ Crash Failure ] -crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:discontiguous_location_and_attribs:* [ Crash Failure ] -crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:max_buffers_and_attribs:* [ Crash Failure ] -crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:overlapping_attributes:* [ Crash Failure ] -crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:array_stride_zero:* [ Crash Failure ] -crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:vertex_buffer_used_multiple_times_interleaved:* [ Crash Failure ] -crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:vertex_buffer_used_multiple_times_overlapped:* [ Crash Failure ] - # and possibly crbug.com/1231840 crbug.com/1213657 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:web_platform,copyToTexture,canvas:copy_contents_from_gl_context_canvas:* [ Slow Crash Failure Timeout ] @@ -287,8 +272,6 @@ # Crashes on pipeline compilation in the driver. crbug.com/tint/993 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:shader,execution,robust_access:linear_memory:storageClass="workgroup";atomic=false;baseType="bool";* [ Skip ] -crbug.com/dawn/1013 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:unused_bindings_in_pipeline:* [ Crash Failure ] - # Crashes or fails with "Backing is being accessed by both GL and Vulkan" crbug.com/1234041 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:* [ Skip ]
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt index 449e57b..bba3d84 100644 --- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt +++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt
@@ -1,21 +1,21 @@ This is a testharness.js-based test. -PASS source should not be hidden -PASS track should not be hidden -FAIL area should be hidden assert_equals: expected "none" but got "inline" -FAIL base should be hidden assert_equals: expected "none" but got "inline" -FAIL basefont should be hidden assert_equals: expected "none" but got "inline" -PASS datalist should be hidden -PASS head should be hidden -PASS link should be hidden -PASS meta should be hidden -FAIL noembed should be hidden assert_equals: expected "none" but got "inline" -PASS noframes should be hidden -PASS param should be hidden -PASS rp should be hidden -PASS script should be hidden -PASS style should be hidden -PASS template should be hidden -PASS title should be hidden -PASS [hidden] element should be hidden +PASS source should not be display:none +PASS track should not be display:none +FAIL area should be display:none assert_true: expected true got false +PASS base should be display:none +PASS basefont should be display:none +PASS datalist should be display:none +PASS head should be display:none +PASS link should be display:none +PASS meta should be display:none +PASS noembed should be display:none +PASS noframes should be display:none +PASS param should be display:none +PASS rp should be display:none +PASS script should be display:none +PASS style should be display:none +PASS template should be display:none +PASS title should be display:none +PASS [hidden] element should be display:none Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html index 4286681..577f32d 100644 --- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html +++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html
@@ -13,23 +13,27 @@ "noframes", "param", "rp", "script", "style", "template", "title", ]; +function isDisplayNone(element) { + return getComputedStyle(element).display === "none"; +} + for (let name of kNotHiddenElementLocalNames) { test(function() { let element = document.createElement(name); document.body.appendChild(element); - assert_equals(getComputedStyle(element).display, "inline"); - }, `${name} should not be hidden`); + assert_false(isDisplayNone(element)); + }, `${name} should not be display:none`); } for (let name of kHiddenElementLocalNames) { test(function() { let element = document.createElement(name); document.body.appendChild(element); - assert_equals(getComputedStyle(element).display, "none"); - }, `${name} should be hidden`); + assert_true(isDisplayNone(element)); + }, `${name} should be display:none`); } test(function() { - assert_equals(getComputedStyle(document.querySelector("[hidden]")).display, "none"); -}, `[hidden] element should be hidden`); + assert_true(isDisplayNone(document.querySelector("[hidden]"))); +}, `[hidden] element should be display:none`); </script>
diff --git a/third_party/blink/web_tests/fast/forms/access-key-for-all-elements.html b/third_party/blink/web_tests/fast/forms/access-key-for-all-elements.html index ed1388a..a25bea1 100644 --- a/third_party/blink/web_tests/fast/forms/access-key-for-all-elements.html +++ b/third_party/blink/web_tests/fast/forms/access-key-for-all-elements.html
@@ -29,7 +29,7 @@ const focusableTagNames = [ "a", "abbr", "acronym", "address", "area", "article", "aside", "audio", - "b", "base", "basefont", "bdo", "bgsound", "big", "blockquote", "br", + "b", "bdo", "bgsound", "big", "blockquote", "br", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "dd", "del", "details", "dfn", "dir", "div", "dl", "dt", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", @@ -44,7 +44,7 @@ "table", "tbody", "td", "tfoot", "th", "thead", "tr", "track", "tt", "u", "ul", "var", "video", "wbr", "xmp"]; -const nonFocusableTagNames = ["embed", "head", "link", "meta", "noembed", "noscript", "param", "script", "style", "title"]; +const nonFocusableTagNames = ["base", "basefont", "embed", "head", "link", "meta", "noembed", "noscript", "param", "script", "style", "title"]; function testPressAccessKeyOnElement(element) { var clicked = false;
diff --git a/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled-expected.txt b/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled-expected.txt new file mode 100644 index 0000000..c848543 --- /dev/null +++ b/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled-expected.txt
@@ -0,0 +1,18 @@ +Test ensures that frame scroll position is not restored if the page scrolled away before the history navigation finished. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + +PASS document.scrollingElement.scrollLeft is 100 +PASS document.scrollingElement.scrollTop is 100 +PASS document.scrollingElement.scrollLeft is 200 +PASS document.scrollingElement.scrollTop is 200 +PASS document.scrollingElement.scrollLeft is 300 +PASS document.scrollingElement.scrollTop is 300 +Verify that scroll position is kept correctly during navigation. +PASS document.scrollingElement.scrollLeft is 300 +PASS document.scrollingElement.scrollTop is 300 +PASS successfullyParsed is true + +TEST COMPLETE + +
diff --git a/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled.html b/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled.html new file mode 100644 index 0000000..4e15eba --- /dev/null +++ b/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled.html
@@ -0,0 +1,51 @@ +<!DOCTYPE html> +<style> +#content { + height: 2000px; + width: 2000px; +} +</style> + +<div id='console'></div> +<div id='content'></div> + +<script src="../../resources/js-test.js"></script> +<script> +jsTestIsAsync = true; +setPrintTestResultsLazily(); +description('Test ensures that frame scroll position is not restored if the ' + + 'page scrolled away before the history navigation finished.'); + +// Scroll to (100, 100). +window.scrollTo(100, 100); +shouldBe('document.scrollingElement.scrollLeft', '100'); +shouldBe('document.scrollingElement.scrollTop', '100'); + +// Add a new session history entry with URL fragment #test. +// The previous session history entry will save the current scroll offset. +history.pushState(null, null, '#test'); + +setTimeout(() => { + // Scroll to (200, 200). + window.scrollTo(200, 200); + shouldBe('document.scrollingElement.scrollLeft', '200'); + shouldBe('document.scrollingElement.scrollTop', '200'); + + // Navigate back to the first session history entry. This would normally do a + // scroll restoration back to (100, 100) when the navigation commits. + history.back(); + // However, we immediately scroll to (300, 300). + window.scrollTo(300, 300); + shouldBe('document.scrollingElement.scrollLeft', '300'); + shouldBe('document.scrollingElement.scrollTop', '300'); +}, 0); + +window.addEventListener('hashchange', _ => { + // After the back navigation committed, the scroll position stays at (300, 300). + debug('Verify that scroll position is kept correctly during navigation.'); + shouldBe('document.scrollingElement.scrollLeft', '300'); + shouldBe('document.scrollingElement.scrollTop', '300'); + + finishJSTest(); +}); +</script>
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/tspan-pattern-update-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/tspan-pattern-update-expected.txt index 406950be..59b5c22d 100644 --- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/tspan-pattern-update-expected.txt +++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/tspan-pattern-update-expected.txt
@@ -6,7 +6,7 @@ "contentsOpaque": true, "backgroundColor": "#FFFFFF", "invalidations": [ - [8, 8, 300, 100] + [108, 8, 100, 100] ] } ]
diff --git a/third_party/blink/web_tests/http/tests/history/.htaccess b/third_party/blink/web_tests/http/tests/history/.htaccess index 0c64a7af..fbcc3fab 100644 --- a/third_party/blink/web_tests/http/tests/history/.htaccess +++ b/third_party/blink/web_tests/http/tests/history/.htaccess
@@ -13,3 +13,8 @@ <Files "dynamic-frame-creation-order-onload-handler.html"> Header set Cache-Control "no-store" </Files> +# replacestate-post-to-get-2.html is not compatible with back-forward cache. +# The following setting disables back-forward cache. +<Files "replacestate-post-to-get-2.html"> +Header set Cache-Control "no-store" +</Files>
diff --git a/third_party/blink/web_tests/http/tests/media/media-controls-live-timeline.html b/third_party/blink/web_tests/http/tests/media/media-controls-live-timeline.html new file mode 100644 index 0000000..85a6729 --- /dev/null +++ b/third_party/blink/web_tests/http/tests/media/media-controls-live-timeline.html
@@ -0,0 +1,39 @@ +<title>Test timeline work properly when playing live video.</title> +<script src="/w3c/resources/testharness.js"></script> +<script src="/w3c/resources/testharnessreport.js"></script> +<script src="media-source/mediasource-util.js"></script> +<script src="../../media-resources/media-controls.js"></script> +<video controls></video> +<script> + const epsilon = 0.1; + const pause_delay = 1; // 1 second. + + mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData){ + mediaElement.play(); + mediaSource.duration = +Infinity; + let timeline = timelineElement(mediaElement); + + // Append all media data for complete playback. + test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer end update.'); + test.expectEvent(mediaElement, 'loadedmetadata', 'Reached HAVE_METADATA'); + test.expectEvent(mediaElement, 'playing', 'Playing media.'); + sourceBuffer.appendBuffer(mediaData); + + test.waitForExpectedEvents(function() { + test.waitForCurrentTimeChange(mediaElement, function() { + assert_approx_equals(Number(timeline.value), Number(timeline.max), + epsilon, "timline thumb should be near the end"); + + mediaElement.onpause = test.step_func(function() { + setTimeout(test.step_func_done(function() { + assert_approx_equals(Number(timeline.max) - Number(timeline.value), + pause_delay, epsilon, + "timeline thumb should be 1s away from the end"); + }), pause_delay * 1000 /* convert to milliseconds */); + }); + + mediaElement.pause(); + }); + }); + }, "Timeline work properly when playing live video"); +</script>
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/tspan-pattern-update-expected.txt b/third_party/blink/web_tests/paint/invalidation/svg/tspan-pattern-update-expected.txt index 56d07a3..7fc8c61 100644 --- a/third_party/blink/web_tests/paint/invalidation/svg/tspan-pattern-update-expected.txt +++ b/third_party/blink/web_tests/paint/invalidation/svg/tspan-pattern-update-expected.txt
@@ -6,7 +6,7 @@ "contentsOpaque": true, "backgroundColor": "#FFFFFF", "invalidations": [ - [8, 8, 300, 100] + [108, 8, 100, 100] ] } ]
diff --git a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png new file mode 100644 index 0000000..5246f64 --- /dev/null +++ b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png b/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png new file mode 100644 index 0000000..b73270cb --- /dev/null +++ b/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-tspan-expected.png b/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-tspan-expected.png new file mode 100644 index 0000000..0d67598 --- /dev/null +++ b/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-tspan-expected.png Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png new file mode 100644 index 0000000..7bb37b7 --- /dev/null +++ b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png Binary files differ
diff --git a/third_party/libusb/BUILD.gn b/third_party/libusb/BUILD.gn index c77e2e2..c60bf106a 100644 --- a/third_party/libusb/BUILD.gn +++ b/third_party/libusb/BUILD.gn
@@ -3,7 +3,7 @@ # found in the LICENSE file. # libusb is only used by //services/device/usb on Windows and macOS. -assert(is_mac) +assert(is_win || is_mac) import("//build/config/chromeos/ui_mode.gni") import("//build/config/features.gni")
diff --git a/third_party/metrics_proto/README.chromium b/third_party/metrics_proto/README.chromium index b0b2832..4a7547c 100644 --- a/third_party/metrics_proto/README.chromium +++ b/third_party/metrics_proto/README.chromium
@@ -1,8 +1,8 @@ Name: Metrics Protos Short Name: metrics_proto URL: This is the canonical public repository -Version: 384565628 -Date: 2021/07/13 UTC +Version: 388133545 +Date: 2021/08/02 UTC License: BSD Security Critical: Yes
diff --git a/third_party/metrics_proto/structured_data.proto b/third_party/metrics_proto/structured_data.proto index 8ce594e..09bb1fb 100644 --- a/third_party/metrics_proto/structured_data.proto +++ b/third_party/metrics_proto/structured_data.proto
@@ -12,7 +12,7 @@ // One structured metrics event, containing several hashed and unhashed metrics // related to a single event type, tied to a single pseudonymous user. // -// Next tag: 4 +// Next tag: 5 message StructuredEventProto { // A per-client, per-profile, per-project ID that is used only for structured // metrics. For projects recorded from Chrome OS's platform2 repository, this @@ -31,10 +31,14 @@ // 1. Metric name. This is a string, and the first 8 bytes of its MD5 hash is // recorded as name_hash. // - // 2. Kind. Each metric can store two kinds of values. + // 2. Kind. Each metric can store three kinds of values. // // - int64. The client supplies an int64 value for the metric, and that - // value is recorded as-is in value_raw. + // value is recorded as-is in value_int64. + // + // - string. The client supplies a string value for the metric, which is + // recorded as-is in value_string. This is sometimes referred to as a + // "raw string" to differentiate from the following. // // - hashed-string. The client supplies an arbitrary string for the metric. // The string itself is not recorded, instead, value_hmac records the @@ -50,9 +54,21 @@ oneof value { fixed64 value_hmac = 2; int64 value_int64 = 3; + string value_string = 4; } } repeated Metric metrics = 3; + + // Type of this event, which determines which log source the event is saved + // into. An event should have type RAW_STRING if and only if the event may + // contain raw string metrics, ie. strings that have not been HMAC'd. The + // UNKNOWN value is considered an error and should never be sent. + enum EventType { + UNKNOWN = 0; + REGULAR = 1; + RAW_STRING = 2; + } + optional EventType event_type = 4; } // The top-level proto for structured metrics. One StructuredDataProto is
diff --git a/third_party/metrics_proto/ukm/source.proto b/third_party/metrics_proto/ukm/source.proto index c58c6ba..abbb756 100644 --- a/third_party/metrics_proto/ukm/source.proto +++ b/third_party/metrics_proto/ukm/source.proto
@@ -34,7 +34,7 @@ } // Source contains data related to a top-level navigation. -// Next tag: 19 +// Next tag: 20 message Source { // The URL scheme, such as HTTP, HTTPS, CHROME_EXTENSION, etc. enum UrlScheme {
diff --git a/tools/android/avd/proto/creation/generic_android27.textpb b/tools/android/avd/proto/creation/generic_android27.textpb index fc437b0..e1c69e8 100644 --- a/tools/android/avd/proto/creation/generic_android27.textpb +++ b/tools/android/avd/proto/creation/generic_android27.textpb
@@ -6,7 +6,7 @@ emulator_package { package_name: "chromium/third_party/android_sdk/public/emulator" - version: "qrZePjKjcYAEId2PuvAx2MQsYIfhgn_DV1qfYiFrGMUC" # 30.6.5 + version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C" # 30.7.5 dest_path: ".emulator_sdk" } @@ -29,4 +29,8 @@ height: 1920 width: 1080 } + advanced_features { + key: "GLESDynamicVersion" + value: "on" + } }
diff --git a/tools/android/avd/proto/creation/generic_android28.textpb b/tools/android/avd/proto/creation/generic_android28.textpb index 64039176..37438ff 100644 --- a/tools/android/avd/proto/creation/generic_android28.textpb +++ b/tools/android/avd/proto/creation/generic_android28.textpb
@@ -6,7 +6,7 @@ emulator_package { package_name: "chromium/third_party/android_sdk/public/emulator" - version: "qrZePjKjcYAEId2PuvAx2MQsYIfhgn_DV1qfYiFrGMUC" # 30.6.5 + version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C" # 30.7.5 dest_path: ".emulator_sdk" } @@ -29,4 +29,8 @@ height: 1920 width: 1080 } + advanced_features { + key: "GLESDynamicVersion" + value: "on" + } }
diff --git a/tools/android/avd/proto/creation/generic_android29.textpb b/tools/android/avd/proto/creation/generic_android29.textpb index c511386..af7d475 100644 --- a/tools/android/avd/proto/creation/generic_android29.textpb +++ b/tools/android/avd/proto/creation/generic_android29.textpb
@@ -6,7 +6,7 @@ emulator_package { package_name: "chromium/third_party/android_sdk/public/emulator" - version: "qrZePjKjcYAEId2PuvAx2MQsYIfhgn_DV1qfYiFrGMUC" # 30.6.5 + version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C" # 30.7.5 dest_path: ".emulator_sdk" } @@ -29,4 +29,8 @@ height: 1920 width: 1080 } + advanced_features { + key: "GLESDynamicVersion" + value: "on" + } }
diff --git a/tools/android/avd/proto/creation/generic_playstore_android27.textpb b/tools/android/avd/proto/creation/generic_playstore_android27.textpb index c1c40cb..94e5a7e4 100644 --- a/tools/android/avd/proto/creation/generic_playstore_android27.textpb +++ b/tools/android/avd/proto/creation/generic_playstore_android27.textpb
@@ -6,7 +6,7 @@ emulator_package { package_name: "chromium/third_party/android_sdk/public/emulator" - version: "qrZePjKjcYAEId2PuvAx2MQsYIfhgn_DV1qfYiFrGMUC" # 30.6.5 + version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C" # 30.7.5 dest_path: ".emulator_sdk" } @@ -29,4 +29,8 @@ height: 1920 width: 1080 } + advanced_features { + key: "GLESDynamicVersion" + value: "on" + } }
diff --git a/tools/android/avd/proto/creation/generic_playstore_android28.textpb b/tools/android/avd/proto/creation/generic_playstore_android28.textpb index 4b252d3..e58b56f 100644 --- a/tools/android/avd/proto/creation/generic_playstore_android28.textpb +++ b/tools/android/avd/proto/creation/generic_playstore_android28.textpb
@@ -6,7 +6,7 @@ emulator_package { package_name: "chromium/third_party/android_sdk/public/emulator" - version: "qrZePjKjcYAEId2PuvAx2MQsYIfhgn_DV1qfYiFrGMUC" # 30.6.5 + version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C" # 30.7.5 dest_path: ".emulator_sdk" } @@ -29,4 +29,8 @@ height: 1920 width: 1080 } + advanced_features { + key: "GLESDynamicVersion" + value: "on" + } }
diff --git a/tools/gritsettings/translation_expectations.pyl b/tools/gritsettings/translation_expectations.pyl index cfd696e..dac4277 100644 --- a/tools/gritsettings/translation_expectations.pyl +++ b/tools/gritsettings/translation_expectations.pyl
@@ -89,9 +89,11 @@ ], }, # The omnibox_pedal_synonyms are translated only into a subset of the languages. + # Note, the list here must match the <translations> list in + # components/omnibox/resources/omnibox_pedal_synonyms.grd or TC pipeline breaks. "omnibox_pedal_synonyms_grd": { "languages": [ - "ar", "es-419", "fr", "ja", "zh-TW", + "ar", "de", "es-419", "fr", "ja", "zh-CN", "zh-TW", ], "files": [ "components/omnibox/resources/omnibox_pedal_synonyms.grd",
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml index 9d7ed52..ec7ef07 100644 --- a/tools/metrics/actions/actions.xml +++ b/tools/metrics/actions/actions.xml
@@ -5377,6 +5377,15 @@ <description>The user selected 'share' option for a Feed card.</description> </action> +<action name="ContentSuggestions.Feed.CardAction.TapPreview"> + <owner>tinazwang@chromium.org</owner> + <owner>feed@chromium.org</owner> + <description> + After long-pressing the feed card and seeing the preview, the user tapped on + preview. + </description> +</action> + <action name="ContentSuggestions.Feed.DeviceOrientationChanged.Landscape"> <owner>adamta@google.org</owner> <owner>sczs@chromium.org</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index c8d842b..0cdaf0c 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -1870,6 +1870,7 @@ <int value="10" label="DINO_PAGE_OFFLINE_CONTENT"/> <int value="11" label="OFFLINE_INDICATOR"/> <int value="12" label="OFFLINE_CONTENT_NOTIFICATION"/> + <int value="13" label="DOWNLOAD_PROGRESS_MESSAGE"/> </enum> <enum name="AndroidEnhancedProtectionPromoAction"> @@ -34437,6 +34438,9 @@ <int value="38" label="After following an active web feed, the user tapped to dismiss the post-follow help dialog."/> + <int value="39" + label="After long-pressing on the feed and seeing the preview, the user + tapped on the preview."/> </enum> <enum name="FeedUserCommandType"> @@ -54858,6 +54862,7 @@ <int value="13" label="GroupedPermission"/> <int value="14" label="PermissionUpdate"/> <int value="15" label="AdsBlocked"/> + <int value="16" label="DownloadProgress"/> </enum> <enum name="MessageLoopProblems"> @@ -81922,25 +81927,59 @@ <enum name="TranslationStatus"> <int value="0" label="Uninitialized"/> - <int value="1" label="Succeeded, from manual translation"/> + <int value="1" + label="Deprecated 07/2021. Succeeded, from manual translation"/> <int value="2" label="Succeeded, automatic translation by user preference"/> <int value="3" label="Succeeded, automatic translation by link"/> - <int value="4" label="Reverted by user, manual translation"/> + <int value="4" + label="Deprecated 07/2021. Reverted by user, manual translation"/> <int value="5" label="Reverted by user, automatic translation"/> <int value="6" label="New translation requested by user"/> <int value="7" label="Abandoned"/> - <int value="8" label="Failed with no error, manual translation"/> + <int value="8" + label="Deprecated 07/2021. Failed with no error, manual translation"/> <int value="9" label="Failed with no error, automatic translation"/> - <int value="10" label="Failed with an error, manual translation"/> + <int value="10" + label="Deprecated 07/2021. Failed with an error, manual translation"/> <int value="11" label="Failed with an error, automatic translation"/> + <int value="12" label="Succeeded, from manual translation from Translate UI"/> + <int value="13" + label="Reverted by user, manual translation from Translate UI"/> + <int value="14" + label="Failed with no error, manual translation from Translate UI"/> + <int value="15" + label="Failed with an error, manual translation from Translate UI"/> + <int value="16" label="Succeeded, from manual translation from Context Menu"/> + <int value="17" + label="Reverted by user, manual translation from Context Menu"/> + <int value="18" + label="Failed with no error, manual translation from Context Menu"/> + <int value="19" + label="Failed with an error, manual translation from Context Menu"/> </enum> <enum name="TranslationType"> <int value="0" label="Uninitialized"/> - <int value="1" label="Manual translation, first translation of page load"/> - <int value="2" label="Manual translation, after page was already translated"/> + <int value="1" + label="Deprecated 07/2021. Manual translation, first translation of + page load"/> + <int value="2" + label="Deprecated 07/2021. Manual translation, after page was already + translated"/> <int value="3" label="Automatic translation by user preference"/> <int value="4" label="Automatic translation by link"/> + <int value="5" + label="Manual translation from Translate UI, first translation of page + load"/> + <int value="6" + label="Manual translation from Translate UI, after page was already + translated"/> + <int value="7" + label="Manual translation from Context Menu, first translation of page + load"/> + <int value="8" + label="Manual translation Context Menu, after page was already + translated"/> </enum> <enum name="TrendingTileEvent"> @@ -87612,6 +87651,13 @@ <int value="2" label="Well-Known supported"/> </enum> +<enum name="WhatsNewLoadEvent"> + <int value="0" label="Load started"/> + <int value="1" label="Load succeeded"/> + <int value="2" label="Load failed and error page whas shown"/> + <int value="3" label="Load failed and was redirected to NTP"/> +</enum> + <enum name="WhitelistedDownloadType"> <int value="0" label="Does not match any whitelists"/> <int value="1" label="URL whitelist"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml index 47dab0d..34bfd9e 100644 --- a/tools/metrics/histograms/metadata/android/histograms.xml +++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -82,6 +82,7 @@ <variant name=".AddToHomescreenIPH"/> <variant name=".AdsBlocked"/> <variant name=".ChromeSurvey"/> + <variant name=".DownloadProgress"/> <variant name=".GeneratedPasswordSaved"/> <variant name=".GroupedPermission"/> <variant name=".MerchantTrust"/>
diff --git a/tools/metrics/histograms/metadata/borealis/histograms.xml b/tools/metrics/histograms/metadata/borealis/histograms.xml index 36268ec..15b405a 100644 --- a/tools/metrics/histograms/metadata/borealis/histograms.xml +++ b/tools/metrics/histograms/metadata/borealis/histograms.xml
@@ -21,6 +21,16 @@ <histograms> +<histogram name="Borealis.AppsInstalledAtLogin" units="apps" + expires_after="2022-12-26"> + <owner>joelhockey@google.com</owner> + <owner>src/chrome/browser/ash/borealis/OWNERS</owner> + <summary> + Records the number of Broealis apps in the registry at login time. This only + logs if Borealis has already benn enabled for the current user. + </summary> +</histogram> + <histogram name="Borealis.Disk.Client.AvailableSpaceAtRequest" units="MB" expires_after="2022-04-08"> <owner>danielng@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml index cf4258d..a432a33 100644 --- a/tools/metrics/histograms/metadata/others/histograms.xml +++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -13707,8 +13707,8 @@ <histogram name="ReportingAndNEL.BackingStoreUpdateOutcome" enum="NetReportingAndNelBackingStoreUpdateOutcome" expires_after="2020-09-09"> - <owner>chlily@chromium.org</owner> - <owner>sburnett@chromium.org</owner> + <owner>yhirano@chromium.org</owner> + <owner>src/net/reporting/OWNERS</owner> <summary> The outcome of updating the backing store. This is recorded whenever a batch of pending operations is committed to the backing store. Success occurs when @@ -13753,8 +13753,8 @@ <histogram name="ReportingAndNEL.InitializeDBOutcome" enum="NetReportingAndNelInitializeDbOutcome" expires_after="2020-09-09"> - <owner>chlily@chromium.org</owner> - <owner>sburnett@chromium.org</owner> + <owner>yhirano@chromium.org</owner> + <owner>src/net/reporting/OWNERS</owner> <summary> The outcome of initializing the SQLite database. This is recorded upon the first load request after startup, which will typically occur upon the first @@ -13764,8 +13764,8 @@ <histogram name="ReportingAndNEL.KillDatabaseResult" enum="BooleanSuccess" expires_after="2020-09-09"> - <owner>chlily@chromium.org</owner> - <owner>sburnett@chromium.org</owner> + <owner>yhirano@chromium.org</owner> + <owner>src/net/reporting/OWNERS</owner> <summary> Whether killing the database because it was corrupted beyond repair succeeded. This is recorded when a fatal SQLite error is detected. @@ -13774,8 +13774,8 @@ <histogram name="ReportingAndNEL.NumberOfLoadedNELPolicies" units="policy count" expires_after="2020-09-09"> - <owner>chlily@chromium.org</owner> - <owner>sburnett@chromium.org</owner> + <owner>yhirano@chromium.org</owner> + <owner>src/net/reporting/OWNERS</owner> <summary> The number of NEL policies loaded from the store. This is recorded when the NetworkErrorLoggingService requests a load from the backing store, which @@ -13785,8 +13785,8 @@ <histogram name="ReportingAndNEL.NumberOfLoadedReportingEndpointGroups" units="endpoint group count" expires_after="2020-09-09"> - <owner>chlily@chromium.org</owner> - <owner>sburnett@chromium.org</owner> + <owner>yhirano@chromium.org</owner> + <owner>src/net/reporting/OWNERS</owner> <summary> The number of Reporting endpoint groups loaded from the store. This is recorded when the ReportingCache requests a load from the backing store, @@ -13797,8 +13797,8 @@ <histogram name="ReportingAndNEL.NumberOfLoadedReportingEndpoints" units="endpoint count" expires_after="2020-09-09"> - <owner>chlily@chromium.org</owner> - <owner>sburnett@chromium.org</owner> + <owner>yhirano@chromium.org</owner> + <owner>src/net/reporting/OWNERS</owner> <summary> The number of Reporting endpoints loaded from the store. This is recorded when the ReportingCache requests a load from the backing store, which @@ -13809,8 +13809,8 @@ <histogram name="ReportingAndNEL.TimeInitializeDB" units="ms" expires_after="2020-09-09"> - <owner>chlily@chromium.org</owner> - <owner>sburnett@chromium.org</owner> + <owner>yhirano@chromium.org</owner> + <owner>src/net/reporting/OWNERS</owner> <summary> The time (ms) taken to initialize the Reporting and NEL DB. This is recorded when the DB is initialized, which typically occurs upon the first network @@ -19868,6 +19868,19 @@ </summary> </histogram> +<histogram name="WhatsNew.LoadEvent" enum="WhatsNewLoadEvent" + expires_after="2022-01-09"> + <owner>rbpotter@chromium.org</owner> + <owner>mahmadi@chromium.org</owner> + <summary> + Records load events for the What's New page when a load event occurs. This + tracks how many times loading remote content is attempted, and whether it + succeeds, fails and results in an error page, or fails and redirects to the + New Tab Page. Load is attempted when the page is first opened, and success + or failure will occur when the network request completes. Desktop only. + </summary> +</histogram> + <histogram name="WheelScrolling.WasLatched" enum="BooleanLatched" expires_after="M77"> <owner>flackr@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/sb_client/histograms.xml b/tools/metrics/histograms/metadata/sb_client/histograms.xml index f1f4615f..c7dd245 100644 --- a/tools/metrics/histograms/metadata/sb_client/histograms.xml +++ b/tools/metrics/histograms/metadata/sb_client/histograms.xml
@@ -182,6 +182,9 @@ <histogram name="SBClientDownload.ExtractDmgFeaturesTimeMedium" units="ms" expires_after="2021-07-21"> + <obsolete> + Removed 08-2021 due to lack of use. + </obsolete> <owner>drubery@chromium.org</owner> <owner>chrome-safebrowsing-alerts@google.com</owner> <owner>rsesek@chromium.org</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json index 763fff0..7dd4fa25 100644 --- a/tools/perf/core/perfetto_binary_roller/binary_deps.json +++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -9,8 +9,8 @@ "remote_path": "perfetto_binaries/trace_processor_shell/win/b846a827ab0b47d0299bc3776d1fb5c067c50d94/trace_processor_shell.exe" }, "mac": { - "hash": "e7000eeeefed60f263b77eea1156606a18304379", - "remote_path": "perfetto_binaries/trace_processor_shell/mac/a6966e3445f5dc2a0d9f735b8c689c5078e4c33e/trace_processor_shell" + "hash": "8399e9e79d05c0012dd60be2ea9542bf9df11e0d", + "remote_path": "perfetto_binaries/trace_processor_shell/mac/b846a827ab0b47d0299bc3776d1fb5c067c50d94/trace_processor_shell" }, "linux_arm64": { "hash": "5074025a2898ec41a872e70a5719e417acb0a380",
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml index 0a750fd..ce9de1a 100644 --- a/tools/traffic_annotation/summary/annotations.xml +++ b/tools/traffic_annotation/summary/annotations.xml
@@ -78,6 +78,7 @@ <item id="data_reduction_proxy_secure_proxy_check" added_in_milestone="62" hash_code="131236802" type="0" deprecated="2020-03-13" content_hash_code="122297136" file_path=""/> <item id="data_reduction_proxy_warmup" added_in_milestone="62" hash_code="8250451" type="0" deprecated="2020-03-13" content_hash_code="6321249" file_path=""/> <item id="desktop_ios_promotion" added_in_milestone="63" hash_code="13694792" type="0" deprecated="2018-11-04" content_hash_code="19776951" file_path=""/> + <item id="desktop_screenshot_save" added_in_milestone="94" hash_code="18870110" type="0" content_hash_code="26509513" os_list="linux,windows" file_path="chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc"/> <item id="device_geolocation_request" added_in_milestone="62" hash_code="77673751" type="0" deprecated="2017-10-20" content_hash_code="97181773" file_path=""/> <item id="device_management_service" added_in_milestone="62" hash_code="117782019" type="0" content_hash_code="104419970" os_list="linux,windows" file_path="components/policy/core/common/cloud/device_management_service.cc"/> <item id="devtools_cdp_network_resource" added_in_milestone="87" hash_code="60744935" type="0" content_hash_code="85833926" os_list="linux,windows" file_path="content/browser/devtools/protocol/devtools_network_resource_loader.cc"/>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml index f91c34c..81279ca 100644 --- a/tools/traffic_annotation/summary/grouping.xml +++ b/tools/traffic_annotation/summary/grouping.xml
@@ -96,6 +96,7 @@ <traffic_annotation unique_id="proxy_config_settings"/> <traffic_annotation unique_id="video_tutorial_fetcher"/> <traffic_annotation unique_id="qr_code_save"/> + <traffic_annotation unique_id="desktop_screenshot_save"/> <traffic_annotation unique_id="safe_browsing_cache_collector"/> <traffic_annotation unique_id="safety_check_update_connectivity"/> <traffic_annotation unique_id="open_search"/>
diff --git a/tools/typescript/definitions/settings_private.d.ts b/tools/typescript/definitions/settings_private.d.ts new file mode 100644 index 0000000..d324129c --- /dev/null +++ b/tools/typescript/definitions/settings_private.d.ts
@@ -0,0 +1,49 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** @fileoverview Definitions for chrome.settingsPrivate API */ +// TODO(crbug.com/1203307): Auto-generate this file. + +declare namespace chrome { + export namespace settingsPrivate { + export enum PrefType { + BOOLEAN = 'BOOLEAN', + NUMBER = 'NUMBER', + STRING = 'STRING', + URL = 'URL', + LIST = 'LIST', + DICTIONARY = 'DICTIONARY', + } + + export enum ControlledBy { + DEVICE_POLICY = 'DEVICE_POLICY', + USER_POLICY = 'USER_POLICY', + OWNER = 'OWNER', + PRIMARY_USER = 'PRIMARY_USER', + EXTENSION = 'EXTENSION', + PARENT = 'PARENT', + CHILD_RESTRICTION = 'CHILD_RESTRICTION', + } + + export enum Enforcement { + ENFORCED = 'ENFORCED', + RECOMMENDED = 'RECOMMENDED', + PARENT_SUPERVISED = 'PARENT_SUPERVISED', + } + + export interface PrefObject { + key: string; + type: PrefType; + value: any; + controlledBy?: ControlledBy; + controlledByName?: string; + enforcement?: Enforcement; + recommendedValue?: any; + userSelectableValues?: Array<any>; + userControlDisabled?: boolean; + extensionId?: string; + extensionCanBeDisabled?: boolean; + } + } +}
diff --git a/ui/base/cocoa/focus_tracker.h b/ui/base/cocoa/focus_tracker.h index a676e08..c89d08a 100644 --- a/ui/base/cocoa/focus_tracker.h +++ b/ui/base/cocoa/focus_tracker.h
@@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef UI_BASE_COCOA_FOCUS_TRACKER_H_ +#define UI_BASE_COCOA_FOCUS_TRACKER_H_ + #import <Cocoa/Cocoa.h> #include "base/component_export.h" @@ -27,3 +30,5 @@ // longer in the view hierarchy under |window|. - (BOOL)restoreFocusInWindow:(NSWindow*)window; @end + +#endif // UI_BASE_COCOA_FOCUS_TRACKER_H_ \ No newline at end of file
diff --git a/ui/file_manager/file_manager/foreground/js/banner_controller.js b/ui/file_manager/file_manager/foreground/js/banner_controller.js index 5f29452..e02836b4 100644 --- a/ui/file_manager/file_manager/foreground/js/banner_controller.js +++ b/ui/file_manager/file_manager/foreground/js/banner_controller.js
@@ -98,14 +98,14 @@ this.localStorageCache_[key] = storedValue; } } - this.reconcile(); + await this.reconcile(); } /** * Loops through all the banners and checks whether they should be shown or * not. If shown, picks the highest priority banner. */ - reconcile() { + async reconcile() { this.currentVolume_ = this.directoryModel_.getCurrentVolumeInfo(); /** @type {?Banner} */ @@ -117,7 +117,7 @@ this.warningBanners_.concat(this.educationalBanners_); for (const banner of orderedBanners) { if (!this.shouldShowBanner_(banner)) { - this.maybeHideBanner_(banner); + this.hideBannerIfShown_(banner); continue; } @@ -127,7 +127,7 @@ } if (bannerToShow) { - this.showBanner_(bannerToShow); + await this.showBanner_(bannerToShow); } } @@ -143,6 +143,13 @@ return false; } + const showLimit = banner.showLimit(); + const timesShown = + this.localStorageCache_[`${banner.tagName}_${VIEW_COUNTER_SUFFIX}`]; + if (showLimit && timesShown >= showLimit) { + return false; + } + return true; } @@ -151,9 +158,15 @@ * @param {!Banner} banner The banner to hide. * @private */ - showBanner_(banner) { + async showBanner_(banner) { if (banner.parentElement !== this.container_) { this.container_.appendChild(/** @type {Node} */ (banner)); + + // Views are set when the banner is first appended to the DOM. This + // denotes a new app session. + const localStorageKey = `${banner.tagName}_${VIEW_COUNTER_SUFFIX}`; + await this.setLocalStorage_( + localStorageKey, this.localStorageCache_[localStorageKey] + 1); } banner.setAttribute('hidden', false); @@ -167,7 +180,7 @@ * @param {!Banner} banner The banner to hide. * @private */ - maybeHideBanner_(banner) { + hideBannerIfShown_(banner) { if (banner.parentElement !== this.container_) { return; } @@ -201,6 +214,26 @@ } /** + * Writes through the localStorage cache to localStorage to ensure values + * are immediately available. + * @param {string} key The key in localStorage to set. + * @param {number} value The value to set the key to in localStorage. + * @private + */ + async setLocalStorage_(key, value) { + if (!this.localStorageCache_.hasOwnProperty(key)) { + console.error(`Key ${key} not found in localStorage cache`); + return; + } + this.localStorageCache_[key] = value; + try { + await xfm.storage.local.setAsync({[key]: value}); + } catch (e) { + console.warn(e.message); + } + } + + /** * Listens for localStorage changes to ensure instance cache is in sync. * @param {!Object<string, !StorageChange>} changes Changes that occurred. * @param {string} areaName One of "sync"|"local"|"managed". @@ -247,3 +280,28 @@ } return false; } + +/** + * Checks if the current sizeStats are below the threshold required to trigger + * the banner to show. + * @param {!Banner.DiskThresholdMinRatio|!Banner.DiskThresholdMinSize|undefined} + * threshold + * @param {?chrome.fileManagerPrivate.MountPointSizeStats|undefined} sizeStats + * @returns {boolean} + */ +export function isBelowThreshold(threshold, sizeStats) { + if (!threshold || !sizeStats) { + return false; + } + if (!sizeStats.remainingSize || !sizeStats.totalSize) { + return false; + } + if (threshold.minSize < sizeStats.remainingSize) { + return false; + } + const currentRatio = sizeStats.remainingSize / sizeStats.totalSize; + if (threshold.minRatio < currentRatio) { + return false; + } + return true; +}
diff --git a/ui/file_manager/file_manager/foreground/js/banner_controller_unittest.m.js b/ui/file_manager/file_manager/foreground/js/banner_controller_unittest.m.js index 70394ae..9a96b04 100644 --- a/ui/file_manager/file_manager/foreground/js/banner_controller_unittest.m.js +++ b/ui/file_manager/file_manager/foreground/js/banner_controller_unittest.m.js
@@ -25,10 +25,11 @@ /** * @typedef {{ - * setAllowedVolumeTypes: function(!Array<!Banner.AllowedVolumeType>), - * reset: function(), - * tagName: string - * }} + * setAllowedVolumeTypes: function(!Array<!Banner.AllowedVolumeType>), + * setShowLimit: function(number), + * reset: function(), + * tagName: string, + * }} */ let TestBanner; @@ -61,8 +62,8 @@ /** * @type {!Banner.AllowedVolumeType} */ -const myFilesAllowedVolumeType = { - type: VolumeManagerCommon.VolumeType.MY_FILES, +const driveAllowedVolumeType = { + type: VolumeManagerCommon.VolumeType.DRIVE, id: null, }; @@ -82,10 +83,17 @@ /** @type {!Array<!Banner.AllowedVolumeType>} */ let allowedVolumeTypes = []; + /** @type {number|undefined} */ + let showLimit; + class FakeBanner extends Banner { allowedVolumeTypes() { return allowedVolumeTypes; } + + showLimit() { + return showLimit; + } } customElements.define(tagName, FakeBanner); @@ -96,6 +104,10 @@ }, reset: () => { allowedVolumeTypes = []; + showLimit = undefined; + }, + setShowLimit: (limit) => { + showLimit = limit; }, // Element.tagName returns uppercase. tagName: tagName.toUpperCase(), @@ -125,7 +137,6 @@ * Asserts that all banners on the DOM are hidden. */ function assertNoVisibleBanners() { - const elements = bannerContainer.children; for (const element of bannerContainer.children) { assertEquals(element.getAttribute('hidden'), 'true'); assertEquals(element.getAttribute('aria-hidden'), 'true'); @@ -257,10 +268,10 @@ // Set the following banners to show: // First Warning Banner shows on Downloads. - // Second Warning Banner shows on My files. + // Second Warning Banner shows on Drive. // First Educational Banner shows on Android Files. testWarningBanners[0].setAllowedVolumeTypes([downloadsAllowedVolumeType]); - testWarningBanners[1].setAllowedVolumeTypes([myFilesAllowedVolumeType]); + testWarningBanners[1].setAllowedVolumeTypes([driveAllowedVolumeType]); testEducationalBanners[0].setAllowedVolumeTypes( [androidFilesAllowedVolumeType]); @@ -268,16 +279,16 @@ await controller.initialize(); assertBannerVisible(testWarningBanners[0]); - // Change volume to My files and verify the second warning banner is showing. + // Change volume to Drives and verify the second warning banner is showing. changeCurrentVolume( - VolumeManagerCommon.VolumeType.MY_FILES, /* volumeId */ null); - controller.reconcile(); + VolumeManagerCommon.VolumeType.DRIVE, /* volumeId */ null); + await controller.reconcile(); assertBannerVisible(testWarningBanners[1]); // Change volume to Android files and verify the educational banner is shown. changeCurrentVolume( VolumeManagerCommon.VolumeType.ANDROID_FILES, /* volumeId */ null); - controller.reconcile(); + await controller.reconcile(); assertBannerVisible(testEducationalBanners[0]); } @@ -294,6 +305,114 @@ // Change current volume to a null volume type and assert no banner is shown. changeCurrentVolume(/* volumeType */ null, /* volumeId */ null); - controller.reconcile(); + await controller.reconcile(); + assertNoVisibleBanners(); +} + +/** + * Test that banners that have hit their show limit are not shown again. + */ +export async function testBannersChangeAfterShowLimitReached() { + // Add 2 educational banners. + controller.setEducationalBannersInOrder([ + testEducationalBanners[0].tagName, + testEducationalBanners[1].tagName, + ]); + + // Set the showLimit for the educational banners. + testEducationalBanners[0].setShowLimit(1); + testEducationalBanners[0].setAllowedVolumeTypes([downloadsAllowedVolumeType]); + testEducationalBanners[1].setShowLimit(3); + testEducationalBanners[1].setAllowedVolumeTypes([downloadsAllowedVolumeType]); + + // The first reconciliation should increment the counter and append to DOM. + await controller.initialize(); + assertBannerVisible(testEducationalBanners[0]); + + // Clearing the DOM should imitate a new Files app session. + bannerContainer.textContent = ''; + await controller.reconcile(); + + // After a new Files app session has started, the previous counter has + // exceeded it's show limit, assert the next priority banner is shown. + assertBannerVisible(testEducationalBanners[1]); +} + +/** + * Test that the show limit is increased only on a per app session. + */ +export async function testChangingVolumesDoesntIncreaseShowTimes() { + // Add 1 educational banner. + controller.setEducationalBannersInOrder([testEducationalBanners[0].tagName]); + + const bannerShowLimit = 3; + testEducationalBanners[0].setAllowedVolumeTypes([downloadsAllowedVolumeType]); + testEducationalBanners[0].setShowLimit(bannerShowLimit); + + await controller.initialize(); + assertBannerVisible(testEducationalBanners[0]); + + // Change directory one more times than the show limit to verify the show + // limit doesn't increase. + for (let i = 0; i < bannerShowLimit + 1; i++) { + // Change directory and verify no banner shown. + changeCurrentVolume(VolumeManagerCommon.VolumeType.DRIVE, null); + await controller.reconcile(); + assertNoVisibleBanners(); + + // Change back to DOWNLOADS and verify banner has been shown. + changeCurrentVolume(VolumeManagerCommon.VolumeType.DOWNLOADS, null); + await controller.reconcile(); + assertBannerVisible(testEducationalBanners[0]); + } +} + +/** + * Test that multiple banners with different allowedVolumeTypes and show limits + * are show at the right stages. This also asserts that banners that don't + * implement showLimit still are shown as expected. + */ +export async function testMultipleBannersAllowedVolumeTypesAndShowLimit() { + controller.setWarningBannersInOrder([ + testWarningBanners[0].tagName, + ]); + controller.setEducationalBannersInOrder([ + testEducationalBanners[0].tagName, + ]); + + // Set the allowed volume types for the warning banners. + testWarningBanners[0].setAllowedVolumeTypes([driveAllowedVolumeType]); + + // Set the showLimit and allowed volume types for the educational banner. + testEducationalBanners[0].setShowLimit(2); + testEducationalBanners[0].setAllowedVolumeTypes([downloadsAllowedVolumeType]); + + // The first reconciliation should increment the counter and append to DOM. + await controller.initialize(); + assertBannerVisible(testEducationalBanners[0]); + + // Change the directory to Drive. + changeCurrentVolume(VolumeManagerCommon.VolumeType.DRIVE, null); + await controller.reconcile(); + assertBannerVisible(testWarningBanners[0]); + + // Start a new Files app session (clearing the container emulates this). + // Change the directory back Downloads. + bannerContainer.textContent = ''; + changeCurrentVolume(VolumeManagerCommon.VolumeType.DOWNLOADS, null); + await controller.reconcile(); + assertBannerVisible(testEducationalBanners[0]); + + // Change back to Drive, warning banner should still be showing. + changeCurrentVolume(VolumeManagerCommon.VolumeType.DRIVE, null); + await controller.reconcile(); + assertBannerVisible(testWarningBanners[0]); + + // Emulate starting a new Files app session and change back to Downloads + // volume. This time the educational banner should no longer be showing as it + // has exceeded it's show limit. + bannerContainer.textContent = ''; + changeCurrentVolume(VolumeManagerCommon.VolumeType.DOWNLOADS, null); + await controller.reconcile(); assertNoVisibleBanners(); }
diff --git a/ui/file_manager/file_manager/foreground/js/banner_util_unittest.m.js b/ui/file_manager/file_manager/foreground/js/banner_util_unittest.m.js index 70af03c5..ae0daac3 100644 --- a/ui/file_manager/file_manager/foreground/js/banner_util_unittest.m.js +++ b/ui/file_manager/file_manager/foreground/js/banner_util_unittest.m.js
@@ -8,7 +8,7 @@ import {Banner} from '../../externs/banner.js'; import {VolumeInfo} from '../../externs/volume_info.js'; -import {isAllowedVolume} from './banner_controller.js'; +import {isAllowedVolume, isBelowThreshold} from './banner_controller.js'; /** @type {!Array<!Banner.AllowedVolumeType>} */ let allowedVolumeTypes = []; @@ -30,6 +30,18 @@ return /** @type {!VolumeInfo} */ (new FakeVolumeInfo()); } +/** + * Creates a chrome.fileManagerPrivate.MountPointSizeStats type. + * @param {number} remainingSize + * @returns {!chrome.fileManagerPrivate.MountPointSizeStats} + */ +function createRemainingSizeStats(remainingSize) { + return { + totalSize: 20 * 1024 * 1024 * 1024, // 20 GB + remainingSize, + }; +} + export function tearDown() { allowedVolumeTypes = []; } @@ -116,3 +128,78 @@ assertFalse(isAllowedVolume(currentVolume, allowedVolumeTypes)); } + +/** + * Test undefined types return false. + */ +export function testUndefinedThresholdAndSizeStats() { + const testMinSizeThreshold = { + type: VolumeManagerCommon.VolumeType.DOWNLOADS, + minSize: 1 * 1024 * 1024 * 1024, // 1 GB + }; + const testMinRatioThreshold = { + type: VolumeManagerCommon.VolumeType.DOWNLOADS, + minSize: 0.1, + }; + const testSizeStats = { + totalSize: 20 * 1024 * 1024 * 1024, // 20 GB + remainingSize: 1 * 1024 * 1024 * 1024, // 1 GB + }; + + assertFalse(isBelowThreshold(undefined, undefined)); + assertFalse(isBelowThreshold(testMinSizeThreshold, undefined)); + assertFalse(isBelowThreshold(testMinRatioThreshold, undefined)); + assertFalse(isBelowThreshold(undefined, testSizeStats)); +} + +/** + * Test that below, equal to and above the minSize threshold returns correct + * results. + */ +export function testMinSizeReturnsCorrectly() { + const createMinSizeThreshold = (minSize) => ({ + type: VolumeManagerCommon.VolumeType.DOWNLOADS, + minSize, + }); + + // Remaining Size: 512 KB, Min Size: 1 GB + assertTrue(isBelowThreshold( + createMinSizeThreshold(1 * 1024 * 1024 * 1024 /* 1 GB */), + createRemainingSizeStats(512 * 1024 * 1024 /* 512 KB */))); + + // Remaining Size: 1 GB, Min Size: 1 GB + assertTrue(isBelowThreshold( + createMinSizeThreshold(1 * 1024 * 1024 * 1024 /* 1 GB */), + createRemainingSizeStats(1 * 1024 * 1024 * 1024 /* 1 GB */))); + + // Remaining Size: 2 GB, Min Size: 1 GB + assertFalse(isBelowThreshold( + createMinSizeThreshold(1 * 1024 * 1024 * 1024 /* 1 GB */), + createRemainingSizeStats(2 * 1024 * 1024 * 1024 /* 2 GB */))); +} + +/** + * Test that below, equal to and above the minRatio threshold returns correct + * results. + */ +export function testMinRatioReturnsCorrectly() { + const createMinRatioThreshold = (minRatio) => ({ + type: VolumeManagerCommon.VolumeType.DOWNLOADS, + minRatio, + }); + + // Remaining Size Ratio: 0.1, Threshold: 0.2 + assertTrue(isBelowThreshold( + createMinRatioThreshold(0.2), + createRemainingSizeStats(2 * 1024 * 1024 * 1024 /* 2 GB */))); + + // Remaining Size Ratio: 0.1, Threshold: 0.1 + assertTrue(isBelowThreshold( + createMinRatioThreshold(0.1), + createRemainingSizeStats(2 * 1024 * 1024 * 1024 /* 2 GB */))); + + // Remaining Size Ratio: 0.1, Threshold: 0.05 + assertFalse(isBelowThreshold( + createMinRatioThreshold(0.05), + createRemainingSizeStats(2 * 1024 * 1024 * 1024 /* 2 GB */))); +}
diff --git a/ui/gfx/icon_util_unittests_resource.h b/ui/gfx/icon_util_unittests_resource.h index 0a50edfd..560a0de9 100644 --- a/ui/gfx/icon_util_unittests_resource.h +++ b/ui/gfx/icon_util_unittests_resource.h
@@ -2,4 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_ +#define UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_ + #define IDR_MAINFRAME 101 + +#endif // UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_ \ No newline at end of file
diff --git a/ui/gfx/test/icc_profiles.h b/ui/gfx/test/icc_profiles.h index 356beaf..8b13d4a 100644 --- a/ui/gfx/test/icc_profiles.h +++ b/ui/gfx/test/icc_profiles.h
@@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef UI_GFX_TEST_ICC_PROFILES_H_ +#define UI_GFX_TEST_ICC_PROFILES_H_ + #include "ui/gfx/icc_profile.h" namespace gfx { @@ -21,3 +24,5 @@ ICCProfile ICCProfileForTestingOvershoot(); } // namespace gfx + +#endif // UI_GFX_TEST_ICC_PROFILES_H_ \ No newline at end of file
diff --git a/ui/ozone/demo/surfaceless_gl_renderer.cc b/ui/ozone/demo/surfaceless_gl_renderer.cc index b854a48..5d7a037 100644 --- a/ui/ozone/demo/surfaceless_gl_renderer.cc +++ b/ui/ozone/demo/surfaceless_gl_renderer.cc
@@ -11,6 +11,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" #include "base/command_line.h" +#include "base/cxx17_backports.h" #include "base/macros.h" #include "base/strings/string_number_conversions.h" #include "base/trace_event/trace_event.h" @@ -174,7 +175,7 @@ base::StringToInt( command_line->GetSwitchValueASCII("enable-overlay").c_str(), &requested_overlay_cnt); - overlay_cnt_ = std::max(1, std::min(kMaxLayers, requested_overlay_cnt)); + overlay_cnt_ = base::clamp(requested_overlay_cnt, 1, kMaxLayers); const gfx::Size overlay_size = gfx::Size(size_.width() / 8, size_.height() / 8);
diff --git a/ui/views/animation/animation_builder.cc b/ui/views/animation/animation_builder.cc index 2247d61..eb13e91 100644 --- a/ui/views/animation/animation_builder.cc +++ b/ui/views/animation/animation_builder.cc
@@ -34,39 +34,6 @@ a.first->layer()->GetAnimator()->StartTogether(a.second); } -AnimationBuilder& AnimationBuilder::SetDuration(base::TimeDelta duration) { - duration_ = duration; - return *this; -} - -AnimationBuilder& AnimationBuilder::SetOpacity(View* view, - float target_opacity) { - AnimationKey key = {view, ui::LayerAnimationElement::OPACITY}; - AddAnimation(key, ui::LayerAnimationElement::CreateOpacityElement( - target_opacity, duration_)); - return *this; -} - -AnimationBuilder& AnimationBuilder::SetRoundedCorners( - View* view, - gfx::RoundedCornersF& rounded_corners) { - AnimationKey key = {view, ui::LayerAnimationElement::ROUNDED_CORNERS}; - AddAnimation(key, ui::LayerAnimationElement::CreateRoundedCornersElement( - rounded_corners, duration_)); - return *this; -} - -AnimationBuilder& AnimationBuilder::Repeat() { - // Go through all empty sequences added in StartSequence() and set the correct - // repeating behavior. - is_sequence_repeating_ = true; - for (auto& animation : animation_sequences_) { - animation_sequences_[animation.first].back()->set_is_repeating( - is_sequence_repeating_); - } - return *this; -} - AnimationBuilder& AnimationBuilder::NewSequence() { // Add an empty sequence for all existing views. If the same property is // animated at the same time in different sequences PreemptionStrategy will @@ -90,6 +57,43 @@ return *this; } +AnimationBuilder& AnimationBuilder::SetDuration(base::TimeDelta duration) { + duration_ = duration; + return *this; +} + +AnimationBuilder& AnimationBuilder::Repeat() { + // Go through all empty sequences added in StartSequence() and set the correct + // repeating behavior. + is_sequence_repeating_ = true; + for (auto& animation : animation_sequences_) { + animation_sequences_[animation.first].back()->set_is_repeating( + is_sequence_repeating_); + } + return *this; +} + +AnimationBuilder& AnimationBuilder::Then() { + return *this; +} + +AnimationBuilder& AnimationBuilder::SetOpacity(View* view, + float target_opacity) { + AnimationKey key = {view, ui::LayerAnimationElement::OPACITY}; + AddAnimation(key, ui::LayerAnimationElement::CreateOpacityElement( + target_opacity, duration_)); + return *this; +} + +AnimationBuilder& AnimationBuilder::SetRoundedCorners( + View* view, + gfx::RoundedCornersF& rounded_corners) { + AnimationKey key = {view, ui::LayerAnimationElement::ROUNDED_CORNERS}; + AddAnimation(key, ui::LayerAnimationElement::CreateRoundedCornersElement( + rounded_corners, duration_)); + return *this; +} + void AnimationBuilder::OnStarted(base::OnceClosure callback) {} void AnimationBuilder::OnEnded(base::OnceClosure callback) {}
diff --git a/ui/views/animation/animation_builder.h b/ui/views/animation/animation_builder.h index 2091ac9..0678b411 100644 --- a/ui/views/animation/animation_builder.h +++ b/ui/views/animation/animation_builder.h
@@ -27,18 +27,22 @@ AnimationBuilder(); ~AnimationBuilder(); + AnimationBuilder& NewSequence(); + AnimationBuilder& EndSequence(); + AnimationBuilder& SetDuration(base::TimeDelta duration); + // No effect if called before NewSequence(); + AnimationBuilder& Repeat(); + + AnimationBuilder& Then(); + // These methods should be changed to OnSetXXX if we integrate with the View // base class. AnimationBuilder& SetOpacity(View* view, float target_opacity); AnimationBuilder& SetRoundedCorners(views::View* view, gfx::RoundedCornersF& rounded_corners); - // No effect if called before NewSequence(); - AnimationBuilder& Repeat(); - AnimationBuilder& NewSequence(); - AnimationBuilder& EndSequence(); // Called when the animation starts. void OnStarted(base::OnceClosure callback);
diff --git a/ui/views/examples/animation_example.cc b/ui/views/examples/animation_example.cc index 7c1f460..2a90f26b 100644 --- a/ui/views/examples/animation_example.cc +++ b/ui/views/examples/animation_example.cc
@@ -176,9 +176,8 @@ .NewSequence() .Repeat() .SetDuration(base::TimeDelta::FromSeconds(2)) - // TODO(elainechien): These two opacity changes will be separated by a - // .Then() call as they happen in sequence. .SetOpacity(view, 0.4f) + .Then() .SetOpacity(view, 0.9f) .EndSequence(); }
diff --git a/weblayer/shell/app/resource.h b/weblayer/shell/app/resource.h index e50a3ae8..aed36c7 100644 --- a/weblayer/shell/app/resource.h +++ b/weblayer/shell/app/resource.h
@@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef WEBLAYER_SHELL_APP_RESOURCE_H_ +#define WEBLAYER_SHELL_APP_RESOURCE_H_ + //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by shell.rc @@ -37,3 +40,5 @@ #define _APS_NEXT_SYMED_VALUE 117 #endif #endif + +#endif // WEBLAYER_SHELL_APP_RESOURCE_H_ \ No newline at end of file