diff --git a/.vpython3 b/.vpython3 index 1ef981d..83763df2 100644 --- a/.vpython3 +++ b/.vpython3
@@ -30,3 +30,38 @@ name: "infra/python/wheels/six-py2_py3" version: "version:1.15.0" > + +# Common utilities. +wheel: < + name: "infra/python/wheels/psutil/${platform}_${py_python}_${py_abi}" + version: "version:5.7.2" +> +wheel: < + name: "infra/python/wheels/requests-py2_py3" + version: "version:2.13.0" +> + +# Used by various python unit tests. +wheel: < + name: "infra/python/wheels/mock-py2_py3" + version: "version:2.0.0" +> +wheel: < + name: "infra/python/wheels/parameterized-py2_py3" + version: "version:0.7.1" +> +wheel: < + name: "infra/python/wheels/pbr-py2_py3" + version: "version:3.0.0" +> + +# Used by: +# build/chromeos/test_runner.py +wheel: < + name: "infra/python/wheels/jsonlines-py2_py3" + version: "version:1.2.0" +> +wheel: < + name: "infra/python/wheels/python-dateutil-py2_py3" + version: "version:2.7.3" +>
diff --git a/BUILD.gn b/BUILD.gn index c2b4aa79..b6af235 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -855,6 +855,7 @@ cr_fuchsia_package("d8_fuchsia_pkg") { testonly = true binary = "//v8:d8" + manifest = "//build/config/fuchsia/tests-with-exec.cmx" package_name_override = "d8" }
diff --git a/DEPS b/DEPS index 10b5b7f..2a8d7bf 100644 --- a/DEPS +++ b/DEPS
@@ -199,11 +199,11 @@ # 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': '0bad6cf1458da91d48233930d28a869f0d3104bf', + 'skia_revision': '00f4769e34d8b2ee5a31c607f339eac37a051fd9', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': '609f77888b667dbb70edc38715437b69f15a4dc4', + 'v8_revision': '89b433d7c3defd04809fba9deb6f8885160044a4', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling swarming_client # and whatever else without interference from each other. @@ -211,7 +211,7 @@ # 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': 'a408ce8349289c8fe457a5ebccb8c4cbac7b9c3f', + 'angle_revision': 'a685db2e9745b615987b1b514dc23cc2fafff7bd', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. @@ -278,7 +278,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling devtools-frontend # and whatever else without interference from each other. - 'devtools_frontend_revision': '8ab2d7e59263642069235798b5a1e357f21fa479', + 'devtools_frontend_revision': '8c4d6591e05e059903cc45d789db075c6f921b1d', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libprotobuf-mutator # and whatever else without interference from each other. @@ -559,7 +559,7 @@ }, 'src/ios/third_party/material_components_ios/src': { - 'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'afe4105debedd8c7b3376b5a37e9beeafb9b368e', + 'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '274ba189abfb3d8065585b64a67f4d4179996e38', 'condition': 'checkout_ios', }, @@ -905,12 +905,12 @@ # For Linux and Chromium OS. 'src/third_party/cros_system_api': { - 'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + 'eb4873381d11280addc4d988514692ddaf4c91ad', + 'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + 'd9b81c687469075834a0bb7cd67d243ebbd719ae', 'condition': 'checkout_linux', }, 'src/third_party/depot_tools': - Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9bdbb8f944a7aa679381d87b64b3042daae9fc7b', + Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '6ff74e1dce4315916851d396e32bc4121e497b0e', 'src/third_party/devtools-frontend/src': Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'), @@ -1275,7 +1275,7 @@ }, 'src/third_party/perfetto': - Var('android_git') + '/platform/external/perfetto.git' + '@' + 'd8241a7a3e4d6e6a8dc6ec6e00d90ad7a93166eb', + Var('android_git') + '/platform/external/perfetto.git' + '@' + '0f2d499389c5de52c1d42310715bf83835e44c48', 'src/third_party/perl': { 'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3', @@ -1487,7 +1487,7 @@ 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@676d3c095a281f5626a00da882274ca253e33f3a', 'src/third_party/vulkan_memory_allocator': - Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '6c656df63da5995a932aafd45b32af1974e497d9', + Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'b1d65a2b3373fe12c622eaab65e5cf21b906d178', # Display server protocol for Linux. 'src/third_party/wayland/src': { @@ -1583,7 +1583,7 @@ Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'), 'src-internal': { - 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@de2f82a60468d9e05188fc465eb945cf144c8aed', + 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@2358e072d21bd7fa3d9f695d30e21b88d52b65d9', 'condition': 'checkout_src_internal', },
diff --git a/WATCHLISTS b/WATCHLISTS index d9478d54..4146126 100644 --- a/WATCHLISTS +++ b/WATCHLISTS
@@ -1464,10 +1464,6 @@ '^components/omnibox/|'\ '^components/search_engines/' }, - 'optimization_guide': { - 'filepath': 'optimization_guide|'\ - 'optimization_hints', - }, 'origin_trials': { 'filepath': 'origin_trial'\ '|OriginTrial'\ @@ -2666,7 +2662,6 @@ 'romax+watch@chromium.org', 'harringtond+watch@google.com'], 'omnibox': ['jdonnelly+watch@chromium.org'], - 'optimization_guide': ['dougarnett+watch-optguide@chromium.org'], 'origin_trials': ['chasej+watch@chromium.org', 'iclelland+watch@chromium.org'], 'ozone': ['kalyan.kondapally@intel.com',
diff --git a/android_webview/lib/aw_main_delegate.cc b/android_webview/lib/aw_main_delegate.cc index 5e54d8c..64a441070 100644 --- a/android_webview/lib/aw_main_delegate.cc +++ b/android_webview/lib/aw_main_delegate.cc
@@ -222,6 +222,9 @@ // which is needed for UseSurfaceLayerForVideo feature. // https://crbug.com/853832 features.EnableIfNotSet(media::kDisableSurfaceLayerForVideo); + + // UseSkiaRenderer requires VizForWebView. + features.DisableIfNotSet(::features::kUseSkiaRenderer); } // WebView does not support overlay fullscreen yet for video overlays.
diff --git a/android_webview/lib/webview_tests.cc b/android_webview/lib/webview_tests.cc index ce8922d..ab2b4cea 100644 --- a/android_webview/lib/webview_tests.cc +++ b/android_webview/lib/webview_tests.cc
@@ -3,6 +3,7 @@ // found in the LICENSE file. #include "android_webview/browser/gfx/gpu_service_webview.h" +#include "base/base_switches.h" #include "base/command_line.h" #include "base/test/test_suite.h" #include "content/public/common/content_switches.h" @@ -11,8 +12,11 @@ #include "ui/gl/test/gl_surface_test_support.h" int main(int argc, char** argv) { - base::CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kSingleProcess); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + command_line->AppendSwitch(switches::kSingleProcess); + command_line->AppendSwitchASCII(switches::kDisableFeatures, + ",Vulkan,UseSkiaRenderer"); + gl::GLSurfaceTestSupport::InitializeNoExtensionsOneOff(); android_webview::GpuServiceWebView::GetInstance(); base::TestSuite test_suite(argc, argv);
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc index fc66926..192f9b21 100644 --- a/ash/accessibility/accessibility_controller_impl.cc +++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -167,6 +167,7 @@ prefs::kAccessibilityMonoAudioEnabled, prefs::kAccessibilityScreenMagnifierEnabled, prefs::kAccessibilityScreenMagnifierFocusFollowingEnabled, + prefs::kAccessibilityScreenMagnifierMousePanningMode, prefs::kAccessibilityScreenMagnifierScale, prefs::kAccessibilitySelectToSpeakEnabled, prefs::kAccessibilitySpokenFeedbackEnabled, @@ -624,6 +625,10 @@ prefs::kAccessibilityAutoclickMenuPosition, static_cast<int>(kDefaultAutoclickMenuPosition), user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF); + registry->RegisterIntegerPref( + prefs::kAccessibilityScreenMagnifierMousePanningMode, + static_cast<int>(MagnifierMousePanningMode::kNone), + user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF); registry->RegisterBooleanPref( prefs::kAccessibilityCaretHighlightEnabled, false, user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
diff --git a/ash/public/cpp/accessibility_controller_enums.h b/ash/public/cpp/accessibility_controller_enums.h index 4375535..09c34ee 100644 --- a/ash/public/cpp/accessibility_controller_enums.h +++ b/ash/public/cpp/accessibility_controller_enums.h
@@ -178,6 +178,24 @@ kSystemDefault, }; +// Mouse panning mode for magnifier. This indicates the way the magnified +// viewport is panned as the mouse is moved across the screen. These values are +// written to prefs so they should not be changed. New values should be added at +// the end. +enum class MagnifierMousePanningMode { + // Default value, indicates no mouse panning mode chosen. + kNone, + + // Continuous panning mode. + kContinuous, + + // Centered panning mode. + kCentered, + + // Edge panning mode. + kEdge, +}; + } // namespace ash #endif // ASH_PUBLIC_CPP_ACCESSIBILITY_CONTROLLER_ENUMS_H_
diff --git a/ash/public/cpp/ash_pref_names.cc b/ash/public/cpp/ash_pref_names.cc index 7f0900f1..5485284c 100644 --- a/ash/public/cpp/ash_pref_names.cc +++ b/ash/public/cpp/ash_pref_names.cc
@@ -32,6 +32,10 @@ // is enabled. const char kAccessibilityScreenMagnifierFocusFollowingEnabled[] = "settings.a11y.screen_magnifier_focus_following"; +// An integer pref which indicates the mouse panning mode for screen magnifier. +// This maps to AccessibilityController::MagnifierMousePanningMode. +const char kAccessibilityScreenMagnifierMousePanningMode[] = + "settings.a11y.screen_magnifier_mouse_panning_mode"; // A boolean pref which determines whether screen magnifier should center // the text input focus. const char kAccessibilityScreenMagnifierCenterFocus[] = @@ -61,7 +65,7 @@ const char kAccessibilityAutoclickDelayMs[] = "settings.a11y.autoclick_delay_ms"; // An integer pref which determines the event type for an autoclick event. This -// maps to mojom::AccessibilityController::AutoclickEventType. +// maps to AccessibilityController::AutoclickEventType. const char kAccessibilityAutoclickEventType[] = "settings.a11y.autoclick_event_type"; // Whether Autoclick should immediately return to left click after performing
diff --git a/ash/public/cpp/ash_pref_names.h b/ash/public/cpp/ash_pref_names.h index bf06d54..4461bd92 100644 --- a/ash/public/cpp/ash_pref_names.h +++ b/ash/public/cpp/ash_pref_names.h
@@ -20,6 +20,8 @@ ASH_PUBLIC_EXPORT extern const char kAccessibilityScreenMagnifierEnabled[]; ASH_PUBLIC_EXPORT extern const char kAccessibilityScreenMagnifierFocusFollowingEnabled[]; +ASH_PUBLIC_EXPORT extern const char + kAccessibilityScreenMagnifierMousePanningMode[]; ASH_PUBLIC_EXPORT extern const char kAccessibilityScreenMagnifierScale[]; ASH_PUBLIC_EXPORT extern const char kAccessibilityVirtualKeyboardEnabled[]; ASH_PUBLIC_EXPORT extern const char kAccessibilityVirtualKeyboardFeatures[];
diff --git a/ash/wm/desks/desk_mini_view.cc b/ash/wm/desks/desk_mini_view.cc index 59aeaf4..2c8f5c12 100644 --- a/ash/wm/desks/desk_mini_view.cc +++ b/ash/wm/desks/desk_mini_view.cc
@@ -36,25 +36,25 @@ constexpr int kMinDeskNameViewWidth = 56; -// Returns the width of the desk preview based on its |preview_height| and the -// aspect ratio of the root window taken from |root_window_size|. -int GetPreviewWidth(const gfx::Size& root_window_size, int preview_height) { - return preview_height * root_window_size.width() / root_window_size.height(); -} - -// The desk preview bounds are proportional to the bounds of the display on -// which it resides, and whether the |compact| layout is used. -gfx::Rect GetDeskPreviewBounds(aura::Window* root_window, bool compact) { - const int preview_height = DeskPreviewView::GetHeight(root_window, compact); - const auto root_size = root_window->bounds().size(); - return gfx::Rect(GetPreviewWidth(root_size, preview_height), preview_height); -} - } // namespace // ----------------------------------------------------------------------------- // DeskMiniView +// static +int DeskMiniView::GetPreviewWidth(const gfx::Size& root_window_size, + int preview_height) { + return preview_height * root_window_size.width() / root_window_size.height(); +} + +// static +gfx::Rect DeskMiniView::GetDeskPreviewBounds(aura::Window* root_window, + bool compact) { + const int preview_height = DeskPreviewView::GetHeight(root_window, compact); + const auto root_size = root_window->bounds().size(); + return gfx::Rect(GetPreviewWidth(root_size, preview_height), preview_height); +} + DeskMiniView::DeskMiniView(DesksBarView* owner_bar, aura::Window* root_window, Desk* desk)
diff --git a/ash/wm/desks/desk_mini_view.h b/ash/wm/desks/desk_mini_view.h index 754142c1..0222d9c60 100644 --- a/ash/wm/desks/desk_mini_view.h +++ b/ash/wm/desks/desk_mini_view.h
@@ -34,6 +34,16 @@ public views::TextfieldController, public views::ViewObserver { public: + // Returns the width of the desk preview based on its |preview_height| and the + // aspect ratio of the root window taken from |root_window_size|. + static int GetPreviewWidth(const gfx::Size& root_window_size, + int preview_height); + + // The desk preview bounds are proportional to the bounds of the display on + // which it resides, and whether the |compact| layout is used. + static gfx::Rect GetDeskPreviewBounds(aura::Window* root_window, + bool compact); + DeskMiniView(DesksBarView* owner_bar, aura::Window* root_window, Desk* desk); ~DeskMiniView() override;
diff --git a/ash/wm/desks/desk_mini_view_animations.cc b/ash/wm/desks/desk_mini_view_animations.cc index 795e67f3..185ed461 100644 --- a/ash/wm/desks/desk_mini_view_animations.cc +++ b/ash/wm/desks/desk_mini_view_animations.cc
@@ -147,8 +147,9 @@ removed_mini_view_->parent()->RemoveChildViewT(removed_mini_view_); if (to_zero_state_) { DCHECK(bar_view_); - bar_view_->zero_state_default_desk_button()->SetVisible(true); - bar_view_->zero_state_new_desk_button()->SetVisible(true); + // Layout the desks bar to make sure the buttons visibilities and button's + // text can be updated correctly while going back to zero state. + bar_view_->Layout(); } }
diff --git a/ash/wm/desks/desks_bar_view.cc b/ash/wm/desks/desks_bar_view.cc index c44a3ef..14e2e79 100644 --- a/ash/wm/desks/desks_bar_view.cc +++ b/ash/wm/desks/desks_bar_view.cc
@@ -272,6 +272,13 @@ gfx::Rect(gfx::Point((desks_bar_bounds.width() - content_width) / 2, kZeroStateY), zero_state_default_desk_button_size)); + // Update this button's text since it may changes while removing a desk + // and going back to the zero state. + zero_state_default_desk_button->UpdateLabelText(); + // Make sure these two buttons are always visible while in zero state bar + // since they are invisible in expanded state bar. + zero_state_default_desk_button->SetVisible(true); + zero_state_new_desk_button->SetVisible(true); zero_state_new_desk_button->SetBoundsRect(gfx::Rect( gfx::Point(zero_state_default_desk_button->bounds().right() + kZeroStateButtonSpacing, @@ -671,7 +678,9 @@ removed_mini_views.push_back(removed_mini_view); removed_mini_views.push_back(mini_views_[0]); mini_views_.clear(); - // Keep current layout until the animation is completed. + // Keep current layout until the animation is completed since the animation + // for going back to zero state is based on the expanded bar's current + // layout. PerformExpandedStateToZeroStateMiniViewAnimation(this, removed_mini_views); return; }
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc index 2318ad12..98c7fbd 100644 --- a/ash/wm/desks/desks_unittests.cc +++ b/ash/wm/desks/desks_unittests.cc
@@ -4319,6 +4319,83 @@ EXPECT_TRUE(new_desk_button->GetEnabled()); } +TEST_F(DesksBentoTest, ZeroStateDeskButtonText) { + UpdateDisplay("1600x1200"); + auto* overview_controller = Shell::Get()->overview_controller(); + overview_controller->StartOverview(); + + auto* root_window = Shell::GetPrimaryRootWindow(); + auto* desks_bar_view = GetOverviewGridForRoot(root_window)->desks_bar_view(); + ASSERT_TRUE(desks_bar_view->IsZeroState()); + // Show the default name "Desk 1" while initializing the desks bar at the + // first time. + EXPECT_EQ(base::UTF8ToUTF16("Desk 1"), + desks_bar_view->zero_state_default_desk_button()->GetText()); + + auto* event_generator = GetEventGenerator(); + ClickOnView(desks_bar_view->zero_state_default_desk_button(), + event_generator); + EXPECT_TRUE(desks_bar_view->mini_views()[0]->desk_name_view()->HasFocus()); + + auto send_key = [this](ui::KeyboardCode key_code, int flags = 0) { + auto* generator = GetEventGenerator(); + generator->PressKey(key_code, flags); + generator->ReleaseKey(key_code, flags); + }; + + // Change the desk name to "test". + send_key(ui::VKEY_T); + send_key(ui::VKEY_E); + send_key(ui::VKEY_S); + send_key(ui::VKEY_T); + send_key(ui::VKEY_RETURN); + overview_controller->EndOverview(); + overview_controller->StartOverview(); + + desks_bar_view = GetOverviewGridForRoot(root_window)->desks_bar_view(); + EXPECT_TRUE(desks_bar_view->IsZeroState()); + // Should show the desk's current name "test" instead of the default name. + EXPECT_EQ(base::UTF8ToUTF16("test"), + desks_bar_view->zero_state_default_desk_button()->GetText()); + + // Create 'Desk 2'. + ClickOnView(desks_bar_view->zero_state_new_desk_button(), event_generator); + EXPECT_FALSE(desks_bar_view->IsZeroState()); + send_key(ui::VKEY_RETURN); + EXPECT_EQ(base::UTF8ToUTF16("Desk 2"), + DesksController::Get()->desks()[1].get()->name()); + + // Close desk 'test' should return to zero state and the zero state default + // desk button should show current desk's name, which is 'Desk 1'. + CloseDeskFromMiniView(desks_bar_view->mini_views()[0], event_generator); + EXPECT_TRUE(desks_bar_view->IsZeroState()); + EXPECT_EQ(base::UTF8ToUTF16("Desk 1"), + desks_bar_view->zero_state_default_desk_button()->GetText()); + + // Set a super long desk name. + ClickOnView(desks_bar_view->zero_state_default_desk_button(), + event_generator); + for (size_t i = 0; i < DeskNameView::kMaxLength + 5; i++) + send_key(ui::VKEY_A); + send_key(ui::VKEY_RETURN); + overview_controller->EndOverview(); + overview_controller->StartOverview(); + + desks_bar_view = GetOverviewGridForRoot(root_window)->desks_bar_view(); + auto* zero_state_default_desk_button = + desks_bar_view->zero_state_default_desk_button(); + base::string16 desk_button_text = zero_state_default_desk_button->GetText(); + base::string16 expected_desk_name(DeskNameView::kMaxLength, L'a'); + // Zero state desk button should show the elided name as the DeskNameView. + EXPECT_EQ(expected_desk_name, + DesksController::Get()->desks()[0].get()->name()); + EXPECT_NE(expected_desk_name, desk_button_text); + EXPECT_TRUE(base::StartsWith(base::UTF16ToUTF8(desk_button_text), "aaa", + base::CompareCase::SENSITIVE)); + EXPECT_FALSE(base::EndsWith(base::UTF16ToUTF8(desk_button_text), "aaa", + base::CompareCase::SENSITIVE)); +} + TEST_F(DesksBentoTest, ReorderDesksByMouse) { auto* desks_controller = DesksController::Get();
diff --git a/ash/wm/desks/expanded_state_new_desk_button.cc b/ash/wm/desks/expanded_state_new_desk_button.cc index 1255fb56..97d0479 100644 --- a/ash/wm/desks/expanded_state_new_desk_button.cc +++ b/ash/wm/desks/expanded_state_new_desk_button.cc
@@ -10,7 +10,6 @@ #include "ash/style/ash_color_provider.h" #include "ash/wm/desks/desk_mini_view.h" #include "ash/wm/desks/desk_name_view.h" -#include "ash/wm/desks/desk_preview_view.h" #include "ash/wm/desks/desks_bar_view.h" #include "ash/wm/desks/zero_state_button.h" #include "ash/wm/overview/overview_controller.h" @@ -29,17 +28,6 @@ constexpr int kCornerRadius = 4; -// The new desk button in expand desks bar in Bento has the same size as the -// desk preview, which is proportional to the size of the display on which it -// resides. -gfx::Rect GetExpandedStateNewDeskButtonBounds(aura::Window* root_window) { - const int preview_height = - DeskPreviewView::GetHeight(root_window, /*compact=*/false); - const auto root_size = root_window->bounds().size(); - return gfx::Rect(preview_height * root_size.width() / root_size.height(), - preview_height); -} - // The button belongs to ExpandedStateNewDeskButton. class ASH_EXPORT InnerNewDeskButton : public DeskButtonBase { public: @@ -123,8 +111,9 @@ if (bar_view_->mini_views().empty()) return; - const gfx::Rect new_desk_button_bounds = GetExpandedStateNewDeskButtonBounds( - bar_view_->GetWidget()->GetNativeWindow()->GetRootWindow()); + const gfx::Rect new_desk_button_bounds = DeskMiniView::GetDeskPreviewBounds( + bar_view_->GetWidget()->GetNativeWindow()->GetRootWindow(), + /*compact=*/false); new_desk_button_->SetBoundsRect(new_desk_button_bounds); const gfx::Size label_size =
diff --git a/ash/wm/desks/zero_state_button.cc b/ash/wm/desks/zero_state_button.cc index ecb0b812..0c86cea0 100644 --- a/ash/wm/desks/zero_state_button.cc +++ b/ash/wm/desks/zero_state_button.cc
@@ -7,11 +7,18 @@ #include "ash/resources/vector_icons/vector_icons.h" #include "ash/strings/grit/ash_strings.h" #include "ash/style/ash_color_provider.h" +#include "ash/wm/desks/desk.h" +#include "ash/wm/desks/desk_mini_view.h" +#include "ash/wm/desks/desk_preview_view.h" #include "ash/wm/desks/desks_bar_view.h" #include "ash/wm/desks/desks_controller.h" #include "ash/wm/wm_highlight_item_border.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/text_constants.h" +#include "ui/gfx/text_elider.h" +#include "ui/views/accessibility/view_accessibility.h" #include "ui/views/animation/ink_drop_impl.h" #include "ui/views/controls/highlight_path_generator.h" @@ -27,6 +34,8 @@ constexpr int kZeroStateNewDeskButtonWidth = 36; +constexpr int kZeroStateDefaultDeskButtonMinWidth = 56; + } // namespace // ----------------------------------------------------------------------------- @@ -48,8 +57,7 @@ SetFocusPainter(nullptr); SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY); - // TODO(minch): A11y of zero state. - SetAccessibleName(base::UTF8ToUTF16(GetClassName())); + SetAccessibleName(l10n_util::GetStringUTF16(IDS_ASH_DESKS_NEW_DESK_BUTTON)); auto border = std::make_unique<WmHighlightItemBorder>(border_corder_radius); border_ptr_ = border.get(); @@ -137,13 +145,15 @@ // ----------------------------------------------------------------------------- // ZeroStateDefaultDeskButton: -// TODO(minch): Show the first desk's current name instead of the default name. ZeroStateDefaultDeskButton::ZeroStateDefaultDeskButton(DesksBarView* bar_view) - : DeskButtonBase( - l10n_util::GetStringUTF16(IDS_ASH_DESKS_DESK_1_MINI_VIEW_TITLE), - kCornerRadius, - kCornerRadius), - bar_view_(bar_view) {} + : DeskButtonBase(DesksController::Get()->desks()[0]->name(), + kCornerRadius, + kCornerRadius), + bar_view_(bar_view) { + GetViewAccessibility().OverrideName( + l10n_util::GetStringFUTF16(IDS_ASH_DESKS_DESK_ACCESSIBLE_NAME, + DesksController::Get()->desks()[0]->name())); +} const char* ZeroStateDefaultDeskButton::GetClassName() const { return "ZeroStateDefaultDeskButton"; @@ -156,10 +166,19 @@ } gfx::Size ZeroStateDefaultDeskButton::CalculatePreferredSize() const { - const gfx::Size label_size = label()->GetPreferredSize(); - return gfx::Size( - label_size.width() + 2 * kZeroStateDefaultButtonHorizontalPadding, - kZeroStateButtonHeight); + auto* root_window = + bar_view_->GetWidget()->GetNativeWindow()->GetRootWindow(); + const int preview_width = DeskMiniView::GetPreviewWidth( + root_window->bounds().size(), + DeskPreviewView::GetHeight(root_window, /*compact=*/false)); + int label_width = 0, label_height = 0; + gfx::Canvas::SizeStringInt(DesksController::Get()->desks()[0]->name(), + gfx::FontList(), &label_width, &label_height, 0, + gfx::Canvas::NO_ELLIPSIS); + const int width = base::ClampToRange( + label_width + 2 * kZeroStateDefaultButtonHorizontalPadding, + kZeroStateDefaultDeskButtonMinWidth, preview_width); + return gfx::Size(width, kZeroStateButtonHeight); } void ZeroStateDefaultDeskButton::OnButtonPressed() { @@ -167,6 +186,13 @@ /*expanding_bar_view=*/true); } +void ZeroStateDefaultDeskButton::UpdateLabelText() { + SetText(gfx::ElideText( + DesksController::Get()->desks()[0]->name(), gfx::FontList(), + bounds().width() - 2 * kZeroStateDefaultButtonHorizontalPadding, + gfx::ELIDE_TAIL)); +} + // ----------------------------------------------------------------------------- // ZeroStateNewDeskButton:
diff --git a/ash/wm/desks/zero_state_button.h b/ash/wm/desks/zero_state_button.h index 15db654..6a67487 100644 --- a/ash/wm/desks/zero_state_button.h +++ b/ash/wm/desks/zero_state_button.h
@@ -44,6 +44,10 @@ virtual void UpdateButtonState() {} + // Updates the label's text of the button. E.g, ZeroStateDefaultDeskButton + // showing the desk's name, which should be updated on desk name changes. + virtual void UpdateLabelText() {} + SkColor GetBackgroundColorForTesting() const { return background_color_; } protected: @@ -90,6 +94,7 @@ void OnThemeChanged() override; gfx::Size CalculatePreferredSize() const override; void OnButtonPressed() override; + void UpdateLabelText() override; private: DesksBarView* bar_view_;
diff --git a/base/memory/shared_memory_security_policy.h b/base/memory/shared_memory_security_policy.h index e581188..bf356bb 100644 --- a/base/memory/shared_memory_security_policy.h +++ b/base/memory/shared_memory_security_policy.h
@@ -7,15 +7,8 @@ #include <stddef.h> -#include "base/base_export.h" #include "base/compiler_specific.h" -namespace mojo { -namespace core { -class ChannelLinux; -} // namespace core -} // namespace mojo - namespace base { namespace subtle { @@ -26,11 +19,10 @@ // mapped. This can help prevent an attacker from spraying the address space of // a process with shared memory mappings to bypass ASLR. For more details, see // https://googleprojectzero.blogspot.com/2019/04/virtually-unlimited-memory-escaping.html -class BASE_EXPORT SharedMemorySecurityPolicy { +class SharedMemorySecurityPolicy { private: friend class subtle::PlatformSharedMemoryRegion; friend class SharedMemoryMapping; - friend class mojo::core::ChannelLinux; // Checks that a mapping with |size| can be created. Returns false if there is // an overflow in internal calculations, or the max limit has been reached.
diff --git a/base/threading/hang_watcher.cc b/base/threading/hang_watcher.cc index 4d1b7e2..cc7f0f3 100644 --- a/base/threading/hang_watcher.cc +++ b/base/threading/hang_watcher.cc
@@ -491,9 +491,7 @@ // static void HangWatcher::RecordHang() { base::debug::DumpWithoutCrashing(); - // Inhibit code folding. - const int line_number = __LINE__; - base::debug::Alias(&line_number); + NO_CODE_FOLDING(); } ScopedClosureRunner HangWatcher::RegisterThreadInternal(
diff --git a/build/chromeos/test_runner.py b/build/chromeos/test_runner.py index 1628d3f..8818677 100755 --- a/build/chromeos/test_runner.py +++ b/build/chromeos/test_runner.py
@@ -22,6 +22,7 @@ import dateutil.parser # pylint: disable=import-error import jsonlines # pylint: disable=import-error import psutil # pylint: disable=import-error +import six CHROMIUM_SRC_PATH = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', '..')) @@ -33,7 +34,10 @@ from pylib.base import result_sink # pylint: disable=import-error from pylib.results import json_results # pylint: disable=import-error -import subprocess32 as subprocess # pylint: disable=import-error +if six.PY2: + import subprocess32 as subprocess # pylint: disable=import-error +else: + import subprocess # pylint: disable=import-error,wrong-import-order DEFAULT_CROS_CACHE = os.path.abspath( os.path.join(CHROMIUM_SRC_PATH, 'build', 'cros_cache')) @@ -161,7 +165,7 @@ logging.info('Running the following command on the device:') logging.info('\n' + '\n'.join(script_contents)) fd, tmp_path = tempfile.mkstemp(suffix='.sh', dir=self._path_to_outdir) - os.fchmod(fd, 0755) + os.fchmod(fd, 0o755) with os.fdopen(fd, 'wb') as f: f.write('\n'.join(script_contents) + '\n') return tmp_path @@ -186,7 +190,7 @@ signal.signal(signal.SIGTERM, _kill_child_procs) - for i in xrange(self._retries + 1): + for i in range(self._retries + 1): logging.info('########################################') logging.info('Test attempt #%d', i) logging.info('########################################') @@ -197,13 +201,13 @@ env=self._test_env) try: test_proc.wait(timeout=self._timeout) - except subprocess.TimeoutExpired: + except subprocess.TimeoutExpired: # pylint: disable=no-member logging.error('Test timed out. Sending SIGTERM.') # SIGTERM the proc and wait 10s for it to close. test_proc.terminate() try: test_proc.wait(timeout=10) - except subprocess.TimeoutExpired: + except subprocess.TimeoutExpired: # pylint: disable=no-member # If it hasn't closed in 10s, SIGKILL it. logging.error('Test did not exit in time. Sending SIGKILL.') test_proc.kill()
diff --git a/build/chromeos/test_runner_test.py b/build/chromeos/test_runner_test.py index 03cc0dc..9e877824b 100755 --- a/build/chromeos/test_runner_test.py +++ b/build/chromeos/test_runner_test.py
@@ -14,6 +14,7 @@ # //.vpython import mock # pylint: disable=import-error from parameterized import parameterized # pylint: disable=import-error +import six import test_runner @@ -80,6 +81,16 @@ ] return expectation + def safeAssertItemsEqual(self, list1, list2): + """A Py3 safe version of assertItemsEqual. + + See https://bugs.python.org/issue17866. + """ + if six.PY3: + self.assertSetEqual(set(list1), set(list2)) + else: + self.assertItemsEqual(list1, list2) + @parameterized.expand([ [True], [False], @@ -116,7 +127,7 @@ expected_cmd.extend(['--start', '--copy-on-write'] if use_vm else ['--device', 'localhost:2222']) expected_cmd.extend(['--', './device_script.sh']) - self.assertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) fd_mock().write.assert_called_once_with( '#!/bin/sh\nexport HOME=/usr/local/tmp\n' @@ -146,7 +157,7 @@ '--tast', 'ui.ChromeLogin' ] - self.assertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) @parameterized.expand([ [True], @@ -169,7 +180,7 @@ '--tast=( "group:mainline" && "dep:chrome" && !informational)', ] - self.assertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) @parameterized.expand([ [True], @@ -197,7 +208,7 @@ '--deploy-lacros', ] - self.assertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) @parameterized.expand([ [True], @@ -220,7 +231,7 @@ '--tast', 'ui.ChromeLogin', '--tast-var', 'key=value' ] - self.assertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) if __name__ == '__main__':
diff --git a/build/config/fuchsia/add_DebugData_service.test-cmx b/build/config/fuchsia/add_DebugData_service.test-cmx new file mode 100644 index 0000000..33fb6b0 --- /dev/null +++ b/build/config/fuchsia/add_DebugData_service.test-cmx
@@ -0,0 +1,7 @@ +{ + "sandbox": { + "services": [ + "fuchsia.debugdata.DebugData" + ] + } +} \ No newline at end of file
diff --git a/build/config/fuchsia/package.gni b/build/config/fuchsia/package.gni index 51ebad63..f69be31 100644 --- a/build/config/fuchsia/package.gni +++ b/build/config/fuchsia/package.gni
@@ -49,25 +49,20 @@ # Process the CMX fragment in |manifest| to get a full manifest. action(_component_cmx_target) { - forward_variables_from(invoker, [ "testonly" ]) - - if (!defined(invoker.manifest)) { - assert(testonly == true) - - # TODO(1019938): switch the default to tests.cmx which doesn't request - # the deprecated-ambient-replace-as-executable feature. - _manifest_fragment = "//build/config/fuchsia/tests-with-exec.cmx" - } else { - _manifest_fragment = invoker.manifest - } + forward_variables_from(invoker, + [ + "deps", + "testonly", + ]) script = "//build/config/fuchsia/build_cmx_from_fragment.py" - inputs = [ _manifest_fragment ] + + inputs = [ invoker.manifest ] outputs = [ _component_manifest ] args = [ "--cmx-fragment", - rebase_path(_manifest_fragment), + rebase_path(invoker.manifest), "--cmx", rebase_path(_component_manifest), "--program",
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1 index 6a039ec..1d827f0 100644 --- a/build/fuchsia/linux.sdk.sha1 +++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@ -0.20210128.1.1 +0.20210128.2.2
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1 index 6a039ec..69de15c 100644 --- a/build/fuchsia/mac.sdk.sha1 +++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@ -0.20210128.1.1 +0.20210128.2.1
diff --git a/chrome/android/expectations/lint-suppressions.xml b/chrome/android/expectations/lint-suppressions.xml index c815938..b6c9e06 100644 --- a/chrome/android/expectations/lint-suppressions.xml +++ b/chrome/android/expectations/lint-suppressions.xml
@@ -274,8 +274,6 @@ <ignore regexp="The resource `R.string.ntp_article_suggestions_section_empty` appears to be unused"/> <!-- 1: TODO(crbug.com/1137985) resource is used in downstream image editor. --> <ignore regexp="The resource `R.string.clear` appears to be unused"/> - <!-- 1: Temporarily suppressed until impelmentation is ready, see https://crbug.com/1166704. --> - <ignore regexp="The resource `R.string.price_drop_spotted_lower_price` appears to be unused"/> <!-- 1: Temporarily suppressed until impelmentation is ready, see https://crbug.com/1169845. --> <ignore regexp="The resource `R.string.notification_category_price_drop` appears to be unused"/> <!-- 4: Temporarily suppressed until impelmentation is ready, see https://crbug.com/1166702 --> @@ -283,12 +281,6 @@ <ignore regexp="The resource `R.string.price_drop_alerts_card_content` appears to be unused"/> <ignore regexp="The resource `R.string.price_drop_alerts_card_settings` appears to be unused"/> <ignore regexp="The resource `R.string.price_drop_alerts_card_get_notified` appears to be unused"/> - <!-- 5: Temporarily suppressed until impelmentation is ready, see https://crbug.com/1163136 --> - <ignore regexp="The resource `R.string.price_tracking_settings` appears to be unused"/> - <ignore regexp="The resource `R.string.track_prices_on_tabs` appears to be unused"/> - <ignore regexp="The resource `R.string.track_prices_on_tabs_description` appears to be unused"/> - <ignore regexp="The resource `R.string.price_drop_alerts` appears to be unused"/> - <ignore regexp="The resource `R.string.price_drop_alerts_description` appears to be unused"/> <!-- Endnote: Please specify number of suppressions when adding more --> </issue> <issue id="VectorPath" severity="ignore"/>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageCardView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageCardView.java index f16e419..93bb887d 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageCardView.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageCardView.java
@@ -8,13 +8,17 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.AttributeSet; +import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData; +import org.chromium.chrome.browser.util.ChromeAccessibilityUtil; import org.chromium.chrome.tab_ui.R; +import org.chromium.components.browser_ui.widget.textbubble.TextBubble; import org.chromium.ui.widget.ButtonCompat; import org.chromium.ui.widget.ChromeImageView; +import org.chromium.ui.widget.ViewRectProvider; import java.lang.ref.WeakReference; @@ -105,4 +109,19 @@ void setPriceInfoBoxStrings(ShoppingPersistedTabData.PriceDrop priceDrop) { mPriceInfoBox.setPriceStrings(priceDrop.price, priceDrop.previousPrice); } + + // TODO(crbug.com/1166704): This method has little to do with this view. Move this function to a + // price tracking UI utility class. + /** + * When user taps on "Show me" on PriceWelcomeMessage, we scroll them to the binding tab, then a + * blue tooltip appears and points to the price drop indicator. + */ + public static void showPriceDropTooltip(View view) { + ViewRectProvider rectProvider = new ViewRectProvider(view); + TextBubble textBubble = new TextBubble(view.getContext(), view, + R.string.price_drop_spotted_lower_price, R.string.price_drop_spotted_lower_price, + true, rectProvider, ChromeAccessibilityUtil.get().isAccessibilityEnabled()); + textBubble.setDismissOnTouchInteraction(true); + textBubble.show(); + } }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageService.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageService.java index c70c6d7..3c18079 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageService.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageService.java
@@ -60,6 +60,14 @@ * @return the index within the {@link TabListModel}. */ int getTabIndexFromTabId(int tabId); + + /** + * This method updates {@link TabProperties#SHOULD_SHOW_PRICE_DROP_TOOLTIP} of the binding + * tab. + * + * @param index The binding tab index in {@link TabListModel}. + */ + void showPriceDropTooltip(int index); } /** @@ -170,8 +178,10 @@ @VisibleForTesting public void review() { assert mPriceTabData != null; - mPriceWelcomeMessageReviewActionProvider.scrollToBindingTab( - mPriceWelcomeMessageProvider.getTabIndexFromTabId(mPriceTabData.bindingTabId)); + int bindingTabIndex = + mPriceWelcomeMessageProvider.getTabIndexFromTabId(mPriceTabData.bindingTabId); + mPriceWelcomeMessageReviewActionProvider.scrollToBindingTab(bindingTabIndex); + mPriceWelcomeMessageProvider.showPriceDropTooltip(bindingTabIndex); PriceTrackingUtilities.disablePriceWelcomeMessageCard(); }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallback.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallback.java index 22e467d..2441cb8 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallback.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallback.java
@@ -144,7 +144,8 @@ TabModel tabModel = mTabModelSelector.getCurrentModel(); if (filter instanceof EmptyTabModelFilter) { tabModel.moveTab(currentTabId, - mModel.indexFromId(currentTabId) + (distance > 0 ? distance + 1 : distance)); + mModel.getTabCardCountsBefore(mModel.indexFromId(currentTabId) + + (distance > 0 ? distance + 1 : distance))); } else if (!mActionsOnAllRelatedTabs) { int destinationIndex = tabModel.indexOf(mTabModelSelector.getTabById(destinationTabId)); tabModel.moveTab(currentTabId, distance > 0 ? destinationIndex + 1 : destinationIndex); @@ -202,7 +203,8 @@ if (selectedViewHolder != null && !mRecyclerView.isComputingLayout() && shouldUpdate) { View selectedItemView = selectedViewHolder.itemView; - onTabMergeToGroup(mSelectedTabIndex, mHoveredTabIndex); + onTabMergeToGroup(mModel.getTabCardCountsBefore(mSelectedTabIndex), + mModel.getTabCardCountsBefore(mHoveredTabIndex)); mRecyclerView.getLayoutManager().removeView(selectedItemView); } } else { @@ -212,7 +214,7 @@ if (mHoveredTabIndex != TabModel.INVALID_TAB_INDEX && shouldUpdate) { mModel.updateHoveredTabForMergeToGroup(mSelectedTabIndex > mHoveredTabIndex ? mHoveredTabIndex - : mHoveredTabIndex - 1, + : mModel.getTabIndexBefore(mHoveredTabIndex), false); } if (mUnGroupTabIndex != TabModel.INVALID_TAB_INDEX) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java index 235735c..a49a25e 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
@@ -241,6 +241,14 @@ // TODO(crbug.com/1157578): Update the model in mediator. model.set(TabProperties.PRICE_DROP, null); } + } else if (TabProperties.SHOULD_SHOW_PRICE_DROP_TOOLTIP == propertyKey) { + if (model.get(TabProperties.SHOULD_SHOW_PRICE_DROP_TOOLTIP)) { + PriceCardView priceCardView = + (PriceCardView) view.fastFindViewById(R.id.price_info_box_outer); + assert priceCardView.getVisibility() == View.VISIBLE; + PriceWelcomeMessageCardView.showPriceDropTooltip( + priceCardView.findViewById(R.id.current_price)); + } } else if (TabProperties.PAGE_INFO_LISTENER == propertyKey) { TabListMediator.TabActionListener listener = model.get(TabProperties.PAGE_INFO_LISTENER);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java index 878c35b..8a19869 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
@@ -341,6 +341,13 @@ } /** + * @see TabListMediator#getPriceWelcomeMessageInsertionIndex(). + */ + int getPriceWelcomeMessageInsertionIndex() { + return mMediator.getPriceWelcomeMessageInsertionIndex(); + } + + /** * @return The container {@link androidx.recyclerview.widget.RecyclerView} that is showing the * tab list UI. */ @@ -457,4 +464,9 @@ public int getTabIndexFromTabId(int tabId) { return mModel.indexFromId(tabId); } + + @Override + public void showPriceDropTooltip(int index) { + mModel.get(index).model.set(TabProperties.SHOULD_SHOW_PRICE_DROP_TOOLTIP, true); + } }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java index f92ea2e..f3d15cf 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -74,6 +74,8 @@ import org.chromium.content_public.browser.NavigationHistory; import org.chromium.content_public.browser.UiThreadTaskTraits; import org.chromium.ui.base.PageTransition; +import org.chromium.ui.modelutil.ListObservable; +import org.chromium.ui.modelutil.ListObservable.ListObserver; import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter; import org.chromium.url.GURL; @@ -297,6 +299,7 @@ private int mNextTabId = Tab.INVALID_TAB_ID; private @UiType int mUiType; private int mSearchChipIconDrawableId; + private GridLayoutManager mGridLayoutManager; private final TabActionListener mTabSelectedListener = new TabActionListener() { @Override @@ -431,6 +434,8 @@ private final TabModelObserver mTabModelObserver; + private ListObserver<Void> mListObserver; + private @Nullable TemplateUrlService.TemplateUrlServiceObserver mTemplateUrlObserver; private TabGroupTitleEditor mTabGroupTitleEditor; @@ -534,18 +539,20 @@ if (mActionsOnAllRelatedTabs) { TabModelFilter filter = mTabModelSelector.getTabModelFilterProvider() .getCurrentTabModelFilter(); - int index = filter.indexOf(tab); - if (index == TabList.INVALID_TAB_INDEX + int filterIndex = filter.indexOf(tab); + if (filterIndex == TabList.INVALID_TAB_INDEX || getRelatedTabsForId(tab.getId()).size() == 1 - || index >= mModel.size()) { + || filterIndex >= mModel.size()) { return; } - Tab currentGroupSelectedTab = filter.getTabAt(index); + Tab currentGroupSelectedTab = filter.getTabAt(filterIndex); - assert mModel.indexFromId(currentGroupSelectedTab.getId()) == index; + int tabListModelIndex = mModel.indexOfNthTabCard(filterIndex); + assert mModel.indexFromId(currentGroupSelectedTab.getId()) == tabListModelIndex; - updateTab(index, PseudoTab.fromTab(currentGroupSelectedTab), - mModel.get(index).model.get(TabProperties.IS_SELECTED), false, false); + updateTab(tabListModelIndex, PseudoTab.fromTab(currentGroupSelectedTab), + mModel.get(tabListModelIndex).model.get(TabProperties.IS_SELECTED), + false, false); } } @@ -560,17 +567,19 @@ // property models. TabModelFilter filter = mTabModelSelector.getTabModelFilterProvider() .getCurrentTabModelFilter(); - int index = filter.indexOf(tab); - if (index == TabList.INVALID_TAB_INDEX) return; - Tab currentGroupSelectedTab = filter.getTabAt(index); + int filterIndex = filter.indexOf(tab); + if (filterIndex == TabList.INVALID_TAB_INDEX) return; + Tab currentGroupSelectedTab = filter.getTabAt(filterIndex); // TabModel and TabListModel may be in the process of syncing up through // restoring. Examples of this situation are switching between light/dark mode // in incognito, exiting multi-window mode, etc. - if (mModel.indexFromId(currentGroupSelectedTab.getId()) != index) { + int tabListModelIndex = mModel.indexOfNthTabCard(filterIndex); + if (mModel.indexFromId(currentGroupSelectedTab.getId()) != tabListModelIndex) { return; } - updateTab(index, PseudoTab.fromTab(currentGroupSelectedTab), - mModel.get(index).model.get(TabProperties.IS_SELECTED), false, false); + updateTab(tabListModelIndex, PseudoTab.fromTab(currentGroupSelectedTab), + mModel.get(tabListModelIndex).model.get(TabProperties.IS_SELECTED), + false, false); } } @@ -587,7 +596,7 @@ instanceof TabGroupModelFilter) { return; } - onTabMoved(newIndex, curIndex); + onTabMoved(mModel.indexOfNthTabCard(newIndex), mModel.indexOfNthTabCard(curIndex)); } @Override @@ -637,12 +646,12 @@ int nextTabId = Tab.INVALID_TAB_ID; if (mModel.size() > 1) { - PropertyModel nextCardModel = closingTabIndex == 0 - ? mModel.get(closingTabIndex + 1).model - : mModel.get(closingTabIndex - 1).model; - nextTabId = nextCardModel.get(CARD_TYPE) == TAB - ? nextCardModel.get(TabProperties.TAB_ID) - : Tab.INVALID_TAB_ID; + int nextTabIndex = closingTabIndex == 0 + ? mModel.getTabIndexAfter(closingTabIndex) + : mModel.getTabIndexBefore(closingTabIndex); + nextTabId = nextTabIndex == TabModel.INVALID_TAB_INDEX + ? Tab.INVALID_TAB_ID + : mModel.get(nextTabIndex).model.get(TabProperties.TAB_ID); } return TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), nextTabId); @@ -652,6 +661,35 @@ mTabGridItemTouchHelperCallback = new TabGridItemTouchHelperCallback(mModel, mTabModelSelector, mTabClosedListener, mTabGridDialogHandler, mComponentName, mActionsOnAllRelatedTabs); + + // Right now we need to update layout only if there is a price welcome message card in tab + // switcher. + if (mMode == TabListMode.GRID && mUiType != UiType.SELECTABLE + && TabUiFeatureUtilities.isPriceTrackingEnabled()) { + mListObserver = new ListObserver<Void>() { + @Override + public void onItemRangeInserted(ListObservable source, int index, int count) { + updateLayout(); + } + + @Override + public void onItemRangeRemoved(ListObservable source, int index, int count) { + updateLayout(); + } + + @Override + public void onItemRangeChanged( + ListObservable<Void> source, int index, int count, @Nullable Void payload) { + updateLayout(); + } + + @Override + public void onItemMoved(ListObservable source, int curIndex, int newIndex) { + updateLayout(); + } + }; + mModel.addObserver(mListObserver); + } } public void initWithNative(Profile profile) { @@ -693,21 +731,22 @@ return; } Tab currentSelectedTab = mTabModelSelector.getCurrentTab(); - int index = TabModelUtils.getTabIndexById( + int filterIndex = TabModelUtils.getTabIndexById( mTabModelSelector.getTabModelFilterProvider() .getCurrentTabModelFilter(), movedTab.getId()); - addTabInfoToModel(PseudoTab.fromTab(movedTab), index, + addTabInfoToModel(PseudoTab.fromTab(movedTab), + mModel.indexOfNthTabCard(filterIndex), currentSelectedTab.getId() == movedTab.getId()); boolean isSelected = mTabModelSelector.getCurrentTabId() == filter.getTabAt(prevFilterIndex).getId(); - updateTab(prevFilterIndex, + updateTab(mModel.indexOfNthTabCard(prevFilterIndex), PseudoTab.fromTab(filter.getTabAt(prevFilterIndex)), isSelected, true, false); } else { - int curIndex = mModel.indexFromId(movedTab.getId()); - if (!isValidMovePosition(curIndex)) return; - mModel.removeAt(curIndex); + int curTabListModelIndex = mModel.indexFromId(movedTab.getId()); + if (!isValidMovePosition(curTabListModelIndex)) return; + mModel.removeAt(curTabListModelIndex); if (mTabGridDialogHandler != null) { mTabGridDialogHandler.updateDialogContent(isUngroupingLastTabInGroup ? Tab.INVALID_TAB_ID @@ -734,10 +773,10 @@ RecordUserAction.record("TabGrid.Drag.DropToMerge"); } - desIndex = srcIndex > desIndex ? desIndex : desIndex - 1; + desIndex = srcIndex > desIndex ? desIndex : mModel.getTabIndexBefore(desIndex); Tab newSelectedTab = mTabModelSelector.getTabModelFilterProvider() .getCurrentTabModelFilter() - .getTabAt(desIndex); + .getTabAt(mModel.getTabCardCountsBefore(desIndex)); boolean isSelected = mTabModelSelector.getCurrentTab() == newSelectedTab; updateTab(desIndex, PseudoTab.fromTab(newSelectedTab), isSelected, true, false); } @@ -756,7 +795,8 @@ int curPosition = mModel.indexFromId(currentGroupSelectedTab.getId()); if (curPosition == TabModel.INVALID_TAB_INDEX) { // Sync TabListModel with updated TabGroupModelFilter. - int indexToUpdate = filter.indexOf(tabModel.getTabAt(tabModelOldIndex)); + int indexToUpdate = mModel.indexOfNthTabCard( + filter.indexOf(tabModel.getTabAt(tabModelOldIndex))); mModel.updateTabListModelIdForGroup(currentGroupSelectedTab, indexToUpdate); curPosition = mModel.indexFromId(currentGroupSelectedTab.getId()); } @@ -772,8 +812,8 @@ mTabModelSelector, destinationTab); int newPosition = mModel.indexFromId(destinationGroupSelectedTab.getId()); if (newPosition == TabModel.INVALID_TAB_INDEX) { - int indexToUpdate = filter.indexOf(destinationTab) - + (tabModelNewIndex > tabModelOldIndex ? 1 : -1); + int indexToUpdate = mModel.indexOfNthTabCard(filter.indexOf(destinationTab) + + (tabModelNewIndex > tabModelOldIndex ? 1 : -1)); mModel.updateTabListModelIdForGroup( destinationGroupSelectedTab, indexToUpdate); newPosition = mModel.indexFromId(destinationGroupSelectedTab.getId()); @@ -882,9 +922,9 @@ index = related.indexOf(tab); if (index == -1) return TabList.INVALID_TAB_INDEX; } else { - index = TabModelUtils.getTabIndexById( + index = mModel.indexOfNthTabCard(TabModelUtils.getTabIndexById( mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter(), - tab.getId()); + tab.getId())); // TODO(wychen): the title (tab count in the group) is wrong when it's not the last // tab added in the group. } @@ -947,9 +987,11 @@ return tabsCount == 0; } if (tabs.size() != tabsCount) return false; - for (int i = 0; i < tabs.size(); i++) { + int tabsIndex = 0; + for (int i = 0; i < mModel.size(); i++) { if (mModel.get(i).model.get(CARD_TYPE) == TAB - && mModel.get(i).model.get(TabProperties.TAB_ID) != tabs.get(i).getId()) { + && mModel.get(i).model.get(TabProperties.TAB_ID) + != tabs.get(tabsIndex++).getId()) { return false; } } @@ -977,7 +1019,7 @@ for (int i = 0; i < tabsList.size(); i++) { PseudoTab tab = tabsList.get(i); boolean isSelected = mTabModelSelector.getCurrentTabId() == tab.getId(); - updateTab(i, tab, isSelected, false, quickMode); + updateTab(mModel.indexOfNthTabCard(i), tab, isSelected, false, quickMode); } return true; } @@ -1048,6 +1090,7 @@ } mModel.get(index).model.set(TabProperties.TAB_SELECTED_LISTENER, tabSelectedListener); mModel.get(index).model.set(TabProperties.IS_SELECTED, isSelected); + mModel.get(index).model.set(TabProperties.SHOULD_SHOW_PRICE_DROP_TOOLTIP, false); mModel.get(index).model.set(TabProperties.TITLE, getLatestTitleForTab(pseudoTab)); mModel.get(index).model.set( TabProperties.TAB_CLOSED_LISTENER, isRealTab ? mTabClosedListener : null); @@ -1107,12 +1150,14 @@ @Override public void onConfigurationChanged(Configuration newConfig) { updateSpanCountForOrientation(manager, newConfig.orientation); + if (mMode == TabListMode.GRID && mUiType != UiType.SELECTABLE) updateLayout(); } @Override public void onLowMemory() {} }; mContext.registerComponentCallbacks(mComponentCallbacks); + mGridLayoutManager = manager; } /** @@ -1190,6 +1235,9 @@ * Destroy any members that needs clean up. */ public void destroy() { + if (mListObserver != null) { + mModel.removeObserver(mListObserver); + } TabModel tabModel = mTabModelSelector.getCurrentModel(); if (tabModel != null) { for (int i = 0; i < tabModel.getCount(); i++) { @@ -1284,6 +1332,7 @@ tabstripFaviconBackgroundDrawableId) .with(TabProperties.ACCESSIBILITY_DELEGATE, mAccessibilityDelegate) .with(TabProperties.PRICE_DROP, null) + .with(TabProperties.SHOULD_SHOW_PRICE_DROP_TOOLTIP, false) .with(CARD_TYPE, TAB) .build(); @@ -1590,6 +1639,60 @@ return false; } + /** + * The PriceWelcomeMessage should be in view when user enters the tab switcher, so we put it + * exactly below the currently selected tab. + * + * @return Where the PriceWelcomeMessage should be inserted in the {@link TabListModel} when + * user enters the tab switcher. + */ + int getPriceWelcomeMessageInsertionIndex() { + assert mGridLayoutManager != null; + int spanCount = mGridLayoutManager.getSpanCount(); + int selectedTabIndex = mModel.getIndexForSelectedTab(); + int indexBelowSelectedTab = (selectedTabIndex / spanCount + 1) * spanCount; + int indexAfterLastTab = mModel.getTabIndexBefore(mModel.size()) + 1; + return Math.min(indexBelowSelectedTab, indexAfterLastTab); + } + + /** + * Update the layout of tab switcher to make it compact. Because now we have messages within the + * tabs like PriceWelcomeMessage and these messages take up the entire row, some operations like + * closing a tab above the message card will leave a blank grid, so we need to update the + * layout. + */ + @VisibleForTesting + void updateLayout() { + // Right now we need to update layout only if there is a price welcome message card in tab + // switcher. + if (PriceTrackingUtilities.isPriceWelcomeMessageCardDisabled()) return; + assert mGridLayoutManager != null; + int spanCount = mGridLayoutManager.getSpanCount(); + GridLayoutManager.SpanSizeLookup spanSizeLookup = mGridLayoutManager.getSpanSizeLookup(); + int spanSizeSumForCurrentRow = 0; + int index = 0; + for (; index < mModel.size(); index++) { + spanSizeSumForCurrentRow += spanSizeLookup.getSpanSize(index); + if (spanSizeSumForCurrentRow == spanCount) { + // This row is compact, we clear and recount the spanSize for next row. + spanSizeSumForCurrentRow = 0; + } else if (spanSizeSumForCurrentRow > spanCount) { + // Find a blank grid and break. + if (mModel.get(index).type == TabProperties.UiType.PRICE_WELCOME) break; + spanSizeSumForCurrentRow = 0; + } + } + if (spanSizeSumForCurrentRow <= spanCount) return; + int blankSize = spanCount - (spanSizeSumForCurrentRow - spanSizeLookup.getSpanSize(index)); + for (int i = index + 1; i < mModel.size(); i++) { + if (spanSizeLookup.getSpanSize(i) > blankSize) continue; + mModel.move(i, index); + // We should return after one move because once item moved, updateLayout() will be + // called again. + return; + } + } + @VisibleForTesting View.AccessibilityDelegate getAccessibilityDelegateForTesting() { return mAccessibilityDelegate;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListModel.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListModel.java index bd0ed3fa..e230557 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListModel.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListModel.java
@@ -71,6 +71,88 @@ } /** + * Find the Nth TAB card in the {@link TabListModel}. + * @param n N of the Nth TAB card. + * @return The index of Nth TAB card in the {@link TabListModel}. + */ + public int indexOfNthTabCard(int n) { + if (n < 0) return TabModel.INVALID_TAB_INDEX; + int tabCount = 0; + int lastTabIndex = TabModel.INVALID_TAB_INDEX; + for (int i = 0; i < size(); i++) { + PropertyModel model = get(i).model; + if (model.get(CARD_TYPE) == TAB) { + if (tabCount++ == n) return i; + lastTabIndex = i; + } + } + // If n >= tabCount, we return the index after the last tab. This is used when adding a new + // tab. + return lastTabIndex + 1; + } + + /** + * Get the number of TAB cards before the given index in TabListModel. + * @param index The given index in TabListModel. + * @return The number of TAB cards before the given index. + */ + public int getTabCardCountsBefore(int index) { + if (index < 0) return TabModel.INVALID_TAB_INDEX; + if (index > size()) index = size(); + int tabCount = 0; + for (int i = 0; i < index; i++) { + if (get(i).model.get(CARD_TYPE) == TAB) tabCount++; + } + return tabCount; + } + + /** + * Get the index of the last tab before the given index in TabListModel. + * @param index The given index in TabListModel. + * @return The index of the tab before the given index in TabListModel. + */ + public int getTabIndexBefore(int index) { + for (int i = index - 1; i >= 0; i--) { + if (get(i).model.get(CARD_TYPE) == TAB) return i; + } + return TabModel.INVALID_TAB_INDEX; + } + + /** + * Get the index of the first tab after the given index in TabListModel. + * @param index The given index in TabListModel. + * @return The index of the tab after the given index in TabListModel. + */ + public int getTabIndexAfter(int index) { + for (int i = index + 1; i < size(); i++) { + if (get(i).model.get(CARD_TYPE) == TAB) return i; + } + return TabModel.INVALID_TAB_INDEX; + } + + /** + * Get the index of currently selected tab in TabListModel. + * @return The index within the model. + */ + public int getIndexForSelectedTab() { + int selectedTabCount = 0; + int tabCount = 0; + int firstSelectedTabIndex = TabModel.INVALID_TAB_INDEX; + for (int i = size() - 1; i >= 0; i--) { + PropertyModel model = get(i).model; + if (model.get(CARD_TYPE) != TAB) continue; + if (model.get(TabProperties.IS_SELECTED)) { + selectedTabCount++; + firstSelectedTabIndex = i; + } + tabCount++; + } + assert (selectedTabCount == 1 || tabCount == 0) + : "There should be exactly one selected tab or no tabs at all when calling this method"; + return firstSelectedTabIndex; + } + + /** * Get the index that matches a message item that has the given message type. * @param messageType The message type to match. * @return The index within the model.
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java index 2a00bd5..c27e915 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
@@ -123,6 +123,9 @@ public static final WritableObjectPropertyKey<ShoppingPersistedTabData.PriceDrop> PRICE_DROP = new WritableObjectPropertyKey<>(); + public static final WritableBooleanPropertyKey SHOULD_SHOW_PRICE_DROP_TOOLTIP = + new WritableBooleanPropertyKey(); + public static final PropertyKey[] ALL_KEYS_TAB_GRID = new PropertyKey[] {TAB_ID, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON, THUMBNAIL_FETCHER, IPH_PROVIDER, TITLE, IS_SELECTED, CHECKED_DRAWABLE_STATE_LIST, CREATE_GROUP_LISTENER, CARD_ALPHA, @@ -132,7 +135,7 @@ SELECTABLE_TAB_ACTION_BUTTON_SELECTED_BACKGROUND, URL_DOMAIN, ACCESSIBILITY_DELEGATE, SEARCH_QUERY, PAGE_INFO_LISTENER, PAGE_INFO_ICON_DRAWABLE_ID, CARD_TYPE, CONTENT_DESCRIPTION_STRING, CLOSE_BUTTON_DESCRIPTION_STRING, - SHOPPING_PERSISTED_TAB_DATA_FETCHER, PRICE_DROP}; + SHOPPING_PERSISTED_TAB_DATA_FETCHER, PRICE_DROP, SHOULD_SHOW_PRICE_DROP_TOOLTIP}; public static final PropertyKey[] ALL_KEYS_TAB_STRIP = new PropertyKey[] {TAB_ID, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java index 973d00a..9ff1c51 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
@@ -461,12 +461,10 @@ public boolean resetWithTabs( @Nullable List<PseudoTab> tabs, boolean quickMode, boolean mruMode) { mMediator.registerFirstMeaningfulPaintRecorder(); - // Make sure that before resetWithListOfTabs, there are no messages in the middle of tabs in - // our TabListModel. - removeAllAppendedMessage(); boolean showQuickly = mTabListCoordinator.resetWithListOfTabs(tabs, quickMode, mruMode); if (showQuickly) { mTabListCoordinator.removeSpecialListItem(TabProperties.UiType.NEW_TAB_TILE, 0); + removeAllAppendedMessage(); } int cardsCount = tabs == null ? 0 : tabs.size(); @@ -503,7 +501,8 @@ mMessageCardProviderCoordinator.getMessageItems(); for (int i = 0; i < messages.size(); i++) { if (messages.get(i).type == MessageService.MessageType.PRICE_WELCOME) { - mTabListCoordinator.addSpecialListItemToEnd( + mTabListCoordinator.addSpecialListItem( + mTabListCoordinator.getPriceWelcomeMessageInsertionIndex(), TabProperties.UiType.PRICE_WELCOME, messages.get(i).model); } else { mTabListCoordinator.addSpecialListItemToEnd( @@ -533,7 +532,8 @@ for (int i = 0; i < messages.size(); i++) { if (messages.get(i).type == MessageService.MessageType.PRICE_WELCOME) { mTabListCoordinator.addSpecialListItem( - index + i, TabProperties.UiType.PRICE_WELCOME, messages.get(i).model); + mTabListCoordinator.getPriceWelcomeMessageInsertionIndex(), + TabProperties.UiType.PRICE_WELCOME, messages.get(i).model); } else { mTabListCoordinator.addSpecialListItem( index + i, TabProperties.UiType.MESSAGE, messages.get(i).model); @@ -549,7 +549,8 @@ mMessageCardProviderCoordinator.getNextMessageItemForType(messageType); if (nextMessage == null) return; if (messageType == MessageService.MessageType.PRICE_WELCOME) { - mTabListCoordinator.addSpecialListItemToEnd( + mTabListCoordinator.addSpecialListItem( + mTabListCoordinator.getPriceWelcomeMessageInsertionIndex(), TabProperties.UiType.PRICE_WELCOME, nextMessage.model); } else { mTabListCoordinator.addSpecialListItemToEnd(
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageServiceUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageServiceUnitTest.java index b7d0cbde..0c1c81f 100644 --- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageServiceUnitTest.java +++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/PriceWelcomeMessageServiceUnitTest.java
@@ -154,6 +154,7 @@ mMessageService.preparePriceMessage(); mMessageService.review(); verify(mReviewActionProvider).scrollToBindingTab(index); + verify(mMessageProvider).showPriceDropTooltip(index); assertTrue(PriceTrackingUtilities.isPriceWelcomeMessageCardDisabled()); }
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java index 2a54a971..3666ec0 100644 --- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java +++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -236,6 +236,8 @@ @Mock GridLayoutManager mGridLayoutManager; @Mock + GridLayoutManager.SpanSizeLookup mSpanSizeLookup; + @Mock Profile mProfile; @Mock Tracker mTracker; @@ -342,6 +344,10 @@ .openTabGridDialog(any(Tab.class)); doNothing().when(mContext).registerComponentCallbacks(mComponentCallbacksCaptor.capture()); doReturn(mGridLayoutManager).when(mRecyclerView).getLayoutManager(); + doReturn(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_PORTRAIT) + .when(mGridLayoutManager) + .getSpanCount(); + doReturn(mSpanSizeLookup).when(mGridLayoutManager).getSpanSizeLookup(); doReturn(TAB1_DOMAIN) .when(mUrlUtilitiesJniMock) .getDomainAndRegistry(eq(TAB1_URL), anyBoolean()); @@ -367,6 +373,18 @@ assertThat(mTabModelObserverCaptor.getAllValues().isEmpty(), equalTo(true)); mMediator.initWithNative(mProfile); assertThat(mTabModelObserverCaptor.getAllValues().isEmpty(), equalTo(false)); + + doAnswer(invocation -> { + int position = invocation.getArgument(0); + int itemType = mModel.get(position).type; + if (itemType == TabProperties.UiType.MESSAGE + || itemType == TabProperties.UiType.PRICE_WELCOME) { + return mGridLayoutManager.getSpanCount(); + } + return 1; + }) + .when(mSpanSizeLookup) + .getSpanSize(anyInt()); } @After @@ -2153,6 +2171,7 @@ mTabContentManager::getTabThumbnailWithCallback, mTitleProvider, mTabListFaviconProvider, true, null, null, null, getClass().getSimpleName(), TabProperties.UiType.CLOSABLE); + mMediator.registerOrientationListener(mGridLayoutManager); mMediator.initWithNative(mProfile); initAndAssertAllProperties(); @@ -2177,6 +2196,7 @@ mTabContentManager::getTabThumbnailWithCallback, mTitleProvider, mTabListFaviconProvider, true, null, null, null, getClass().getSimpleName(), TabProperties.UiType.CLOSABLE); + mMediator.registerOrientationListener(mGridLayoutManager); mMediator.initWithNative(mProfile); initAndAssertAllProperties(); @@ -2209,6 +2229,170 @@ } @Test + public void testGetPriceWelcomeMessageInsertionIndex() { + initWithThreeTabs(); + + doReturn(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_PORTRAIT) + .when(mGridLayoutManager) + .getSpanCount(); + assertThat(mMediator.getPriceWelcomeMessageInsertionIndex(), equalTo(2)); + + doReturn(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_LANDSCAPE) + .when(mGridLayoutManager) + .getSpanCount(); + assertThat(mMediator.getPriceWelcomeMessageInsertionIndex(), equalTo(3)); + } + + @Test + public void testUpdateLayout_PriceWelcome() { + initAndAssertAllProperties(); + addSpecialItem(1, TabProperties.UiType.PRICE_WELCOME, PRICE_WELCOME); + assertThat(mModel.lastIndexForMessageItemFromType(PRICE_WELCOME), equalTo(1)); + + doAnswer(invocation -> { + int position = invocation.getArgument(0); + int itemType = mModel.get(position).type; + if (itemType == TabProperties.UiType.PRICE_WELCOME) { + return mGridLayoutManager.getSpanCount(); + } + return 1; + }) + .when(mSpanSizeLookup) + .getSpanSize(anyInt()); + mMediator.updateLayout(); + assertThat(mModel.lastIndexForMessageItemFromType(PRICE_WELCOME), equalTo(1)); + PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean( + PriceTrackingUtilities.PRICE_WELCOME_MESSAGE_CARD, true); + mMediator.updateLayout(); + assertThat(mModel.lastIndexForMessageItemFromType(PRICE_WELCOME), equalTo(2)); + } + + @Test + public void testUpdateLayout_Divider() { + initAndAssertAllProperties(); + addSpecialItem(1, TabProperties.UiType.DIVIDER, 0); + assertThat(mModel.get(1).type, equalTo(TabProperties.UiType.DIVIDER)); + + doAnswer(invocation -> { + int position = invocation.getArgument(0); + int itemType = mModel.get(position).type; + if (itemType == TabProperties.UiType.DIVIDER) { + return mGridLayoutManager.getSpanCount(); + } + return 1; + }) + .when(mSpanSizeLookup) + .getSpanSize(anyInt()); + PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean( + PriceTrackingUtilities.PRICE_WELCOME_MESSAGE_CARD, true); + mMediator.updateLayout(); + assertThat(mModel.get(1).type, equalTo(TabProperties.UiType.DIVIDER)); + } + + @Test + public void testIndexOfNthTabCard() { + initAndAssertAllProperties(); + addSpecialItem(1, TabProperties.UiType.PRICE_WELCOME, PRICE_WELCOME); + + assertThat(mModel.lastIndexForMessageItemFromType(PRICE_WELCOME), equalTo(1)); + assertThat(mModel.indexOfNthTabCard(-1), equalTo(TabModel.INVALID_TAB_INDEX)); + assertThat(mModel.indexOfNthTabCard(0), equalTo(0)); + assertThat(mModel.indexOfNthTabCard(1), equalTo(2)); + assertThat(mModel.indexOfNthTabCard(2), equalTo(3)); + } + + @Test + public void testGetTabCardCountsBefore() { + initAndAssertAllProperties(); + addSpecialItem(1, TabProperties.UiType.PRICE_WELCOME, PRICE_WELCOME); + + assertThat(mModel.lastIndexForMessageItemFromType(PRICE_WELCOME), equalTo(1)); + assertThat(mModel.getTabCardCountsBefore(-1), equalTo(TabModel.INVALID_TAB_INDEX)); + assertThat(mModel.getTabCardCountsBefore(0), equalTo(0)); + assertThat(mModel.getTabCardCountsBefore(1), equalTo(1)); + assertThat(mModel.getTabCardCountsBefore(2), equalTo(1)); + assertThat(mModel.getTabCardCountsBefore(3), equalTo(2)); + } + + @Test + public void testGetTabIndexBefore() { + initAndAssertAllProperties(); + addSpecialItem(1, TabProperties.UiType.PRICE_WELCOME, PRICE_WELCOME); + assertThat(mModel.lastIndexForMessageItemFromType(PRICE_WELCOME), equalTo(1)); + assertThat(mModel.getTabIndexBefore(2), equalTo(0)); + assertThat(mModel.getTabIndexBefore(0), equalTo(TabModel.INVALID_TAB_INDEX)); + } + + @Test + public void testGetTabIndexAfter() { + initAndAssertAllProperties(); + addSpecialItem(1, TabProperties.UiType.PRICE_WELCOME, PRICE_WELCOME); + assertThat(mModel.lastIndexForMessageItemFromType(PRICE_WELCOME), equalTo(1)); + assertThat(mModel.getTabIndexAfter(0), equalTo(2)); + assertThat(mModel.getTabIndexAfter(2), equalTo(TabModel.INVALID_TAB_INDEX)); + } + + @Test + public void testGetIndexForSelectedTab() { + initAndAssertAllProperties(); + assertThat(mModel.getIndexForSelectedTab(), equalTo(0)); + } + + @Test(expected = AssertionError.class) + public void testGetIndexForSelectedTab_multipleSelectedTabs() { + initAndAssertAllProperties(); + mModel.get(0).model.set(TabProperties.IS_SELECTED, true); + mModel.get(1).model.set(TabProperties.IS_SELECTED, true); + mModel.getIndexForSelectedTab(); + } + + @Test(expected = AssertionError.class) + public void testGetIndexForSelectedTab_noSelectedTabs() { + initAndAssertAllProperties(); + mModel.get(0).model.set(TabProperties.IS_SELECTED, false); + mModel.get(1).model.set(TabProperties.IS_SELECTED, false); + mModel.getIndexForSelectedTab(); + } + + @Test + public void testListObserver_OnItemRangeInserted() { + TabUiFeatureUtilities.ENABLE_PRICE_TRACKING.setForTesting(true); + mMediator = new TabListMediator(mContext, mModel, TabListMode.GRID, mTabModelSelector, + mTabContentManager::getTabThumbnailWithCallback, mTitleProvider, + mTabListFaviconProvider, true, null, null, null, getClass().getSimpleName(), + TabProperties.UiType.CLOSABLE); + mMediator.registerOrientationListener(mGridLayoutManager); + mMediator.initWithNative(mProfile); + initAndAssertAllProperties(); + + PropertyModel model = mock(PropertyModel.class); + when(model.get(CARD_TYPE)).thenReturn(MESSAGE); + when(model.get(MESSAGE_TYPE)).thenReturn(PRICE_WELCOME); + mMediator.addSpecialItemToModel(1, TabProperties.UiType.PRICE_WELCOME, model); + assertThat(mModel.lastIndexForMessageItemFromType(PRICE_WELCOME), equalTo(2)); + } + + @Test + public void testListObserver_OnItemRangeRemoved() { + TabUiFeatureUtilities.ENABLE_PRICE_TRACKING.setForTesting(true); + mMediator = new TabListMediator(mContext, mModel, TabListMode.GRID, mTabModelSelector, + mTabContentManager::getTabThumbnailWithCallback, mTitleProvider, + mTabListFaviconProvider, true, null, null, null, getClass().getSimpleName(), + TabProperties.UiType.CLOSABLE); + mMediator.registerOrientationListener(mGridLayoutManager); + mMediator.initWithNative(mProfile); + initWithThreeTabs(); + + PropertyModel model = mock(PropertyModel.class); + when(model.get(CARD_TYPE)).thenReturn(MESSAGE); + when(model.get(MESSAGE_TYPE)).thenReturn(PRICE_WELCOME); + mMediator.addSpecialItemToModel(2, TabProperties.UiType.PRICE_WELCOME, model); + assertThat(mModel.lastIndexForMessageItemFromType(PRICE_WELCOME), equalTo(2)); + mModel.removeAt(0); + assertThat(mModel.lastIndexForMessageItemFromType(PRICE_WELCOME), equalTo(2)); + } + + @Test @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) public void testUpdateFaviconForGroup() { setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); @@ -2549,6 +2733,7 @@ mTabContentManager::getTabThumbnailWithCallback, mTitleProvider, mTabListFaviconProvider, actionOnRelatedTabs, null, null, handler, getClass().getSimpleName(), uiType); + mMediator.registerOrientationListener(mGridLayoutManager); // TabGroupModelFilterObserver is registered when native is ready. assertThat(mTabGroupModelFilterObserverCaptor.getAllValues().isEmpty(), equalTo(true)); @@ -2604,4 +2789,26 @@ .canApplyOptimization( anyLong(), any(GURL.class), anyInt(), any(OptimizationGuideCallback.class)); } + + private void initWithThreeTabs() { + Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); + List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3)); + mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false, false); + assertThat(mModel.size(), equalTo(3)); + assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true)); + assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false)); + assertThat(mModel.get(2).model.get(TabProperties.IS_SELECTED), equalTo(false)); + } + + private void addSpecialItem(int index, @UiType int uiType, int itemIdentifier) { + PropertyModel model = mock(PropertyModel.class); + when(model.get(CARD_TYPE)).thenReturn(MESSAGE); + if (uiType == TabProperties.UiType.MESSAGE + || uiType == TabProperties.UiType.PRICE_WELCOME) { + when(model.get(MESSAGE_TYPE)).thenReturn(itemIdentifier); + } + // Avoid auto-updating the layout when inserting the special card. + doReturn(1).when(mSpanSizeLookup).getSpanSize(anyInt()); + mMediator.addSpecialItemToModel(index, uiType, model); + } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java index ec7e5ba..e609c350 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
@@ -45,7 +45,6 @@ import org.chromium.chrome.browser.xsurface.ProcessScope; import org.chromium.chrome.browser.xsurface.ProcessScopeDependencyProvider; import org.chromium.chrome.modules.image_editor.ImageEditorModuleProvider; -import org.chromium.components.browser_ui.widget.FeatureHighlightProvider; import org.chromium.components.external_intents.AuthenticatorNavigationInterceptor; import org.chromium.components.policy.AppRestrictionsProvider; import org.chromium.components.policy.CombinedPolicyProvider; @@ -268,13 +267,6 @@ } /** - * @return A new {@link FeatureHighlightProvider}. - */ - public FeatureHighlightProvider createFeatureHighlightProvider() { - return new FeatureHighlightProvider(); - } - - /** * @return A new {@link DigitalWellbeingClient} instance. */ public DigitalWellbeingClient createDigitalWellbeingClient() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/AppIndexingUtil.java b/chrome/android/java/src/org/chromium/chrome/browser/AppIndexingUtil.java index a720378..1d6ca47 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/AppIndexingUtil.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/AppIndexingUtil.java
@@ -24,7 +24,6 @@ import org.chromium.components.embedder_support.util.UrlUtilities; import org.chromium.content_public.browser.RenderFrameHost; import org.chromium.content_public.browser.WebContents; -import org.chromium.services.service_manager.InterfaceProvider; import org.chromium.url.GURL; import java.lang.annotation.Retention; @@ -173,10 +172,9 @@ RenderFrameHost mainFrame = webContents.getMainFrame(); if (mainFrame == null) return null; - InterfaceProvider interfaces = mainFrame.getRemoteInterfaces(); - if (interfaces == null) return null; + if (!mainFrame.isRenderFrameCreated()) return null; - return interfaces.getInterface(DocumentMetadata.MANAGER); + return mainFrame.getInterfaceToRendererFrame(DocumentMetadata.MANAGER); } @VisibleForTesting
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 567d6db3..9241dee 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
@@ -431,6 +431,7 @@ this::getCurrentTabCreator, this::isCustomTab, getStatusBarColorController(), ScreenOrientationProvider.getInstance(), this::getNotificationManagerProxy, getTabContentManagerSupplier(), + this::getActivityTabStartupMetricsTracker, /* CompositorViewHolder.Initializer */ this) : overridenCommonsFactory.create(this, mRootUiCoordinator::getBottomSheetController, mTabModelSelectorSupplier, getBrowserControlsManager(), @@ -441,6 +442,7 @@ this, this::getCurrentTabCreator, this::isCustomTab, getStatusBarColorController(), ScreenOrientationProvider.getInstance(), this::getNotificationManagerProxy, getTabContentManagerSupplier(), + this::getActivityTabStartupMetricsTracker, /* CompositorViewHolder.Initializer */ this); return createComponent(commonsModule);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java b/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java index 3a001e2..cc30b34 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java
@@ -25,6 +25,7 @@ import org.chromium.chrome.browser.fullscreen.BrowserControlsManager; import org.chromium.chrome.browser.fullscreen.FullscreenManager; import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; +import org.chromium.chrome.browser.metrics.ActivityTabStartupMetricsTracker; import org.chromium.chrome.browser.tabmodel.TabCreator; import org.chromium.chrome.browser.tabmodel.TabCreatorManager; import org.chromium.chrome.browser.tabmodel.TabModelSelector; @@ -67,6 +68,8 @@ private final ScreenOrientationProvider mScreenOrientationProvider; private final Supplier<NotificationManagerProxy> mNotificationManagerProxySupplier; private final ObservableSupplier<TabContentManager> mTabContentManagerSupplier; + private final Supplier<ActivityTabStartupMetricsTracker> + mActivityTabStartupMetricsTrackerSupplier; private final CompositorViewHolder.Initializer mCompositorViewHolderInitializer; /** See {@link ModuleFactoryOverrides} */ @@ -89,6 +92,7 @@ ScreenOrientationProvider screenOrientationProvider, Supplier<NotificationManagerProxy> notificationManagerProxySupplier, ObservableSupplier<TabContentManager> tabContentManagerSupplier, + Supplier<ActivityTabStartupMetricsTracker> activityTabStartupMetricsTrackerSupplier, CompositorViewHolder.Initializer compositorViewHolderInitializer); } @@ -110,6 +114,7 @@ ScreenOrientationProvider screenOrientationProvider, Supplier<NotificationManagerProxy> notificationManagerProxySupplier, ObservableSupplier<TabContentManager> tabContentManagerSupplier, + Supplier<ActivityTabStartupMetricsTracker> activityTabStartupMetricsTrackerSupplier, CompositorViewHolder.Initializer compositorViewHolderInitializer) { mActivity = activity; mBottomSheetControllerSupplier = bottomSheetControllerSupplier; @@ -132,6 +137,7 @@ mScreenOrientationProvider = screenOrientationProvider; mNotificationManagerProxySupplier = notificationManagerProxySupplier; mTabContentManagerSupplier = tabContentManagerSupplier; + mActivityTabStartupMetricsTrackerSupplier = activityTabStartupMetricsTrackerSupplier; mCompositorViewHolderInitializer = compositorViewHolderInitializer; } @@ -271,6 +277,11 @@ } @Provides + public ActivityTabStartupMetricsTracker provideActivityTabStartupMetricsTracker() { + return mActivityTabStartupMetricsTrackerSupplier.get(); + } + + @Provides public CompositorViewHolder.Initializer provideCompositorViewHolderInitializer() { return mCompositorViewHolderInitializer; }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineDetector.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineDetector.java index 8d6f1d42..3678592 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineDetector.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineDetector.java
@@ -64,6 +64,7 @@ // Effectively offline means that all checks have been passed and the // |mCallback| has been invoked to notify the observers. private boolean mIsEffectivelyOffline; + private boolean mIsEffectivelyOfflineInitialized; // True if the network is offline as detected by the connectivity detector. private boolean mIsOfflineLastReportedByConnectivityDetector; @@ -122,10 +123,12 @@ // Connection state has not changed since |mUpdateOfflineStatusIndicatorDelayedRunnable| // was posted. - if (mIsOfflineLastReportedByConnectivityDetector == mIsEffectivelyOffline) { + if (mIsEffectivelyOfflineInitialized + && mIsOfflineLastReportedByConnectivityDetector == mIsEffectivelyOffline) { return; } mIsEffectivelyOffline = mIsOfflineLastReportedByConnectivityDetector; + mIsEffectivelyOfflineInitialized = true; mCallback.onResult(mIsEffectivelyOffline); if (sLoggingEnabled) { logToAdbConsoleNow("Running mUpdateOfflineStatusIndicatorDelayedRunnable end."); @@ -151,9 +154,9 @@ mIsOfflineLastReportedByConnectivityDetector; mIsOfflineLastReportedByConnectivityDetector = (connectionState != ConnectionState.VALIDATED); - if (previousLastReportedStateByOfflineDetector - == mIsOfflineLastReportedByConnectivityDetector) { - mConnectivityDetectorInitialized = true; + if (mConnectivityDetectorInitialized + && previousLastReportedStateByOfflineDetector + == mIsOfflineLastReportedByConnectivityDetector) { return; }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerV2.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerV2.java index d915a60d..ac1c933e 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerV2.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerV2.java
@@ -15,18 +15,18 @@ import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.Callback; import org.chromium.base.CommandLine; -import org.chromium.base.TimeUtilsJni; import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordUserAction; import org.chromium.base.supplier.ObservableSupplier; import org.chromium.base.supplier.Supplier; import org.chromium.chrome.R; +import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; +import org.chromium.chrome.browser.preferences.SharedPreferencesManager; import org.chromium.chrome.browser.status_indicator.StatusIndicatorCoordinator; import org.chromium.content_public.common.ContentSwitches; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.concurrent.TimeUnit; /** * Class that controls visibility and content of {@link StatusIndicatorCoordinator} to relay @@ -47,6 +47,12 @@ int NUM_ENTRIES = 4; } + /** Clock to use so we can mock the time in tests */ + public interface Clock { + long currentTimeMillis(); + } + private static Clock sClock = System::currentTimeMillis; + static final long STATUS_INDICATOR_WAIT_BEFORE_HIDE_DURATION_MS = 2000; // TODO(tbansal): Consider moving the cooldown logic to OfflineDetector.java. @@ -55,6 +61,9 @@ // frequently. static final long STATUS_INDICATOR_COOLDOWN_BEFORE_NEXT_ACTION_MS = 5000; + public static final String OFFLINE_INDICATOR_SHOWN_DURATION_V2 = + "OfflineIndicator.ShownDurationV2"; + private static OfflineDetector sMockOfflineDetector; private static Supplier<Long> sMockElapsedTimeSupplier; @@ -72,7 +81,8 @@ private Runnable mUpdateStatusIndicatorDelayedRunnable; private long mLastActionTime; private boolean mIsOffline; - private long mTimeShownMs; + private boolean mIsOfflineStateInitialized; + private long mWallTimeShownMs; /** * Constructs the offline indicator. @@ -113,7 +123,15 @@ mShowRunnable = () -> { RecordUserAction.record("OfflineIndicator.Shown"); - mTimeShownMs = TimeUnit.MICROSECONDS.toMillis(TimeUtilsJni.get().getTimeTicksNowUs()); + + // If the value of mWallTimeShownMs is already stored in Prefs, then we keep using that + // value. + if (isWallTimeShownMsInPrefs()) { + mWallTimeShownMs = getWallTimeShownMsFromPrefs(); + } else { + mWallTimeShownMs = sClock.currentTimeMillis(); + setWallTimeShownMsInPrefs(mWallTimeShownMs); + } setLastActionTime(); @@ -135,11 +153,15 @@ mUpdateAndHideRunnable = () -> { RecordUserAction.record("OfflineIndicator.Hidden"); - final long shownDuration = - TimeUnit.MICROSECONDS.toMillis(TimeUtilsJni.get().getTimeTicksNowUs()) - - mTimeShownMs; - RecordHistogram.recordMediumTimesHistogram( - "OfflineIndicator.ShownDuration", shownDuration); + + final long shownDurationWallTimeMs = sClock.currentTimeMillis() - mWallTimeShownMs; + clearWallTimeShownMsFromPrefs(); + + // shownDurationWallTimeMs can be negative in cases where the system time is changed, so + // we want to avoid recording metrics in cases where we know this happened. + if (shownDurationWallTimeMs >= 0) { + recordShownDurationV2Histogram(shownDurationWallTimeMs); + } setLastActionTime(); @@ -175,7 +197,7 @@ } public void onConnectionStateChanged(boolean offline) { - if (mIsOffline == offline) { + if (mIsOfflineStateInitialized && mIsOffline == offline) { return; } @@ -213,6 +235,21 @@ private void updateStatusIndicator(boolean offline) { mIsOffline = offline; + if (!mIsOfflineStateInitialized && !offline) { + if (isWallTimeShownMsInPrefs()) { + // Record any metrics persisted in Prefs when Chrome starts up and is online. + mWallTimeShownMs = getWallTimeShownMsFromPrefs(); + final long shownDurationWallTimeMs = sClock.currentTimeMillis() - mWallTimeShownMs; + clearWallTimeShownMsFromPrefs(); + + if (shownDurationWallTimeMs >= 0) { + recordShownDurationV2Histogram(shownDurationWallTimeMs); + } + } + mIsOfflineStateInitialized = true; + return; + } + mIsOfflineStateInitialized = true; int surfaceState; if (mIsUrlBarFocusedSupplier.get()) { // We should clear the runnable if we would be assigning an unnecessary show or hide @@ -250,6 +287,31 @@ mLastActionTime = getElapsedTime(); } + private void recordShownDurationV2Histogram(long shownDurationMs) { + RecordHistogram.recordLongTimesHistogram100( + OFFLINE_INDICATOR_SHOWN_DURATION_V2, shownDurationMs); + } + + private void setWallTimeShownMsInPrefs(long wallTimeShownMs) { + SharedPreferencesManager.getInstance().writeLong( + ChromePreferenceKeys.OFFLINE_INDICATOR_V2_WALL_TIME_SHOWN_MS, wallTimeShownMs); + } + + private long getWallTimeShownMsFromPrefs() { + return SharedPreferencesManager.getInstance().readLong( + ChromePreferenceKeys.OFFLINE_INDICATOR_V2_WALL_TIME_SHOWN_MS); + } + + private void clearWallTimeShownMsFromPrefs() { + SharedPreferencesManager.getInstance().removeKey( + ChromePreferenceKeys.OFFLINE_INDICATOR_V2_WALL_TIME_SHOWN_MS); + } + + private boolean isWallTimeShownMsInPrefs() { + return SharedPreferencesManager.getInstance().contains( + ChromePreferenceKeys.OFFLINE_INDICATOR_V2_WALL_TIME_SHOWN_MS); + } + @VisibleForTesting static void setMockOfflineDetector(OfflineDetector offlineDetector) { sMockOfflineDetector = offlineDetector; @@ -264,4 +326,9 @@ void setHandlerForTesting(Handler handler) { mHandler = handler; } + + @VisibleForTesting + static void setClockForTesting(Clock clock) { + sClock = clock; + } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java index ae50508..0fc7e5d 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java
@@ -89,9 +89,9 @@ mMediator = new StatusMediator(mModel, mStatusView.getResources(), mStatusView.getContext(), urlBarEditingTextStateProvider, isTablet, forceModelViewReconciliationRunnable, - incognitoStateProvider, locationBarDataProvider, - PermissionDialogController.getInstance(), searchEngineLogoUtils, - templateUrlServiceSupplier, profileSupplier, pageInfoIPHController); + locationBarDataProvider, PermissionDialogController.getInstance(), + searchEngineLogoUtils, templateUrlServiceSupplier, profileSupplier, + pageInfoIPHController); Resources res = mStatusView.getResources(); mMediator.setUrlMinWidth(res.getDimensionPixelSize(R.dimen.location_bar_min_url_width) @@ -176,13 +176,17 @@ } // LocationBarData.Observer implementation - // Using the default empty onIncognitoStateChanged. // Using the default empty onNtpStartedLoading. // Using the default empty onPrimaryColorChanged. // Using the default empty onTitleChanged. // Using the default empty onUrlChanged. @Override + public void onIncognitoStateChanged() { + mMediator.onIncognitoStateChanged(); + } + + @Override public void onSecurityStateChanged() { updateStatusIcon(); updateVerboseStatusVisibility();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java index 1490321..6a714ab 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
@@ -29,7 +29,6 @@ import org.chromium.chrome.browser.omnibox.status.StatusProperties.StatusIconResource; import org.chromium.chrome.browser.page_info.PageInfoIPHController; import org.chromium.chrome.browser.profiles.Profile; -import org.chromium.chrome.browser.tabmodel.IncognitoStateProvider; import org.chromium.chrome.browser.theme.ThemeUtils; import org.chromium.components.browser_ui.site_settings.ContentSettingsResources; import org.chromium.components.browser_ui.site_settings.SingleWebsiteSettings; @@ -44,8 +43,7 @@ /** * Contains the controller logic of the Status component. */ -public class StatusMediator implements IncognitoStateProvider.IncognitoStateObserver, - PermissionDialogController.Observer { +public class StatusMediator implements PermissionDialogController.Observer { private static final int PERMISSION_ICON_DISPLAY_TIMEOUT_MS = 8500; private final PropertyModel mModel; @@ -94,7 +92,6 @@ private float mUrlFocusPercent; private String mSearchEngineLogoUrl; - private boolean mIsIncognito; private Runnable mForceModelViewReconciliationRunnable; // Factors used to offset the animation of the status icon's alpha adjustment. The full formula @@ -108,7 +105,6 @@ public StatusMediator(PropertyModel model, Resources resources, Context context, UrlBarEditingTextStateProvider urlBarEditingTextStateProvider, boolean isTablet, Runnable forceModelViewReconciliationRunnable, - IncognitoStateProvider incognitoStateProvider, LocationBarDataProvider locationBarDataProvider, PermissionDialogController permissionDialogController, SearchEngineLogoUtils searchEngineLogoUtils, @@ -136,10 +132,6 @@ mIsTablet = isTablet; mForceModelViewReconciliationRunnable = forceModelViewReconciliationRunnable; - if (incognitoStateProvider != null) { - incognitoStateProvider.addIncognitoStateObserverAndTrigger(this); - } - mPermissionDialogController = permissionDialogController; mPermissionDialogController.addObserver(this); } @@ -302,7 +294,9 @@ // Extra logic to support extra NTP use cases which show the status icon when animating and when // focused, but hide it when unfocused. void setUrlAnimationFinished(boolean urlHasFocus) { - if (mIsTablet || !mSearchEngineLogoUtils.shouldShowSearchEngineLogo(mIsIncognito)) { + if (mIsTablet + || !mSearchEngineLogoUtils.shouldShowSearchEngineLogo( + mLocationBarDataProvider.isIncognito())) { return; } @@ -328,7 +322,8 @@ // On tablets, the status icon should always be shown so the following logic doesn't apply. assert !mIsTablet : "This logic shouldn't be called on tablets"; - if (!mSearchEngineLogoUtils.shouldShowSearchEngineLogo(mIsIncognito)) { + if (!mSearchEngineLogoUtils.shouldShowSearchEngineLogo( + mLocationBarDataProvider.isIncognito())) { return; } @@ -525,12 +520,14 @@ && mShowStatusIconWhenUrlFocused; // Show the logo unfocused if we're on the NTP. - if (mSearchEngineLogoUtils.shouldShowSearchEngineLogo(mIsIncognito) + if (mSearchEngineLogoUtils.shouldShowSearchEngineLogo( + mLocationBarDataProvider.isIncognito()) && mIsSearchEngineStateSetup && (showIconWhenFocused || showIconWhenScrollingOnNTP)) { - getStatusIconResourceForSearchEngineIcon(mIsIncognito, (statusIconRes) -> { - mModel.set(StatusProperties.STATUS_ICON_RESOURCE, statusIconRes); - }); + getStatusIconResourceForSearchEngineIcon( + mLocationBarDataProvider.isIncognito(), (statusIconRes) -> { + mModel.set(StatusProperties.STATUS_ICON_RESOURCE, statusIconRes); + }); return true; } else { mShouldCancelCustomFavicon = true; @@ -608,7 +605,8 @@ /** Return the resource id for the accessibility description or 0 if none apply. */ private int getAccessibilityDescriptionRes() { if (mUrlHasFocus) { - if (mSearchEngineLogoUtils.shouldShowSearchEngineLogo(mIsIncognito)) { + if (mSearchEngineLogoUtils.shouldShowSearchEngineLogo( + mLocationBarDataProvider.isIncognito())) { return 0; } else if (mShowStatusIconWhenUrlFocused) { return R.string.accessibility_toolbar_btn_site_info; @@ -651,13 +649,10 @@ return urlTextWithAutocomplete; } - @Override - public void onIncognitoStateChanged(boolean isIncognito) { - boolean previousIsIncognito = mIsIncognito; - mIsIncognito = isIncognito; - boolean incognitoBadgeVisible = isIncognito && !mIsTablet; + public void onIncognitoStateChanged() { + boolean incognitoBadgeVisible = mLocationBarDataProvider.isIncognito() && !mIsTablet; mModel.set(StatusProperties.INCOGNITO_BADGE_VISIBLE, incognitoBadgeVisible); - if (previousIsIncognito != isIncognito) reconcileVisualState(); + reconcileVisualState(); } /** @@ -675,8 +670,9 @@ // No reconciliation is needed on tablet because the status icon is always shown. if (mIsTablet) return; - if (!mShowStatusIconWhenUrlFocused || mIsIncognito - || !mSearchEngineLogoUtils.shouldShowSearchEngineLogo(mIsIncognito)) { + if (!mShowStatusIconWhenUrlFocused || mLocationBarDataProvider.isIncognito() + || !mSearchEngineLogoUtils.shouldShowSearchEngineLogo( + mLocationBarDataProvider.isIncognito())) { return; }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestFactory.java index 5b24dd8..4d32b7a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestFactory.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestFactory.java
@@ -23,8 +23,6 @@ import org.chromium.content_public.browser.RenderFrameHost; import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContentsStatics; -import org.chromium.mojo.system.MojoException; -import org.chromium.mojo.system.MojoResult; import org.chromium.payments.mojom.PaymentRequest; import org.chromium.services.service_manager.InterfaceFactory; @@ -145,8 +143,7 @@ public PaymentRequest createImpl() { if (mRenderFrameHost == null) return new InvalidPaymentRequest(); if (!mRenderFrameHost.isFeatureEnabled(FeaturePolicyFeature.PAYMENT)) { - mRenderFrameHost.getRemoteInterfaces().onConnectionError( - new MojoException(MojoResult.PERMISSION_DENIED)); + mRenderFrameHost.terminateRendererDueToBadMessage(241 /*PAYMENTS_WITHOUT_PERMISSION*/); return null; }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityLifecycleUmaTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityLifecycleUmaTracker.java index 3c8487f..554bc689 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityLifecycleUmaTracker.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityLifecycleUmaTracker.java
@@ -21,11 +21,14 @@ import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; import org.chromium.chrome.browser.lifecycle.InflationObserver; import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver; +import org.chromium.chrome.browser.metrics.ActivityTabStartupMetricsTracker; import org.chromium.chrome.browser.metrics.WebApkSplashscreenMetrics; import org.chromium.chrome.browser.metrics.WebApkUma; import javax.inject.Inject; +import dagger.Lazy; + /** * Handles recording user metrics for WebAPK activities. */ @@ -38,6 +41,7 @@ private final ChromeActivity mActivity; private final BrowserServicesIntentDataProvider mIntentDataProvider; private final SplashController mSplashController; + private final Lazy<ActivityTabStartupMetricsTracker> mStartupMetricsTracker; /** The start time that the activity becomes focused in milliseconds since boot. */ private long mStartTime; @@ -46,10 +50,12 @@ public WebApkActivityLifecycleUmaTracker(ChromeActivity<?> activity, BrowserServicesIntentDataProvider intentDataProvider, SplashController splashController, ActivityLifecycleDispatcher lifecycleDispatcher, - WebappDeferredStartupWithStorageHandler deferredStartupWithStorageHandler) { + WebappDeferredStartupWithStorageHandler deferredStartupWithStorageHandler, + Lazy<ActivityTabStartupMetricsTracker> startupMetricsTracker) { mActivity = activity; mIntentDataProvider = intentDataProvider; mSplashController = splashController; + mStartupMetricsTracker = startupMetricsTracker; lifecycleDispatcher.register(this); ApplicationStatus.registerStateListenerForActivity(this, mActivity); @@ -76,8 +82,7 @@ // Decide whether to record startup UMA histograms. This is a similar check to the one done // in ChromeTabbedActivity.performPreInflationStartup refer to the comment there for why. if (!LibraryLoader.getInstance().isInitialized()) { - mActivity.getActivityTabStartupMetricsTracker().trackStartupMetrics( - STARTUP_UMA_HISTOGRAM_SUFFIX); + mStartupMetricsTracker.get().trackStartupMetrics(STARTUP_UMA_HISTOGRAM_SUFFIX); // If there is a saved instance state, then the intent (and its stored timestamp) might // be stale (Android replays intents if there is a recents entry for the activity). if (mActivity.getSavedInstanceState() == null) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/RunningInChromeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/RunningInChromeTest.java index 44745dd..7b850cd3 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/RunningInChromeTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/RunningInChromeTest.java
@@ -87,7 +87,7 @@ compositorViewHolderSupplier, tabCreatorManager, tabCreatorSupplier, isPromotableToTabSupplier, statusBarColorController, screenOrientationProvider, notificationManagerProxySupplier, tabContentManagerSupplier, - compositorViewHolderInitializer) -> { + activityTabStartupMetricsTrackerSupplier, compositorViewHolderInitializer) -> { return new ChromeActivityCommonsModule(activity, bottomSheetController, tabModelSelectorSupplier, browserControlsManager, browserControlsVisibilityManager, browserControlsSizer, fullscreenManager, @@ -98,7 +98,8 @@ screenOrientationProvider, () -> mMockNotificationManager, - tabContentManagerSupplier, compositorViewHolderInitializer); + tabContentManagerSupplier, activityTabStartupMetricsTrackerSupplier, + compositorViewHolderInitializer); }); @Rule
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadForegroundServiceManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadForegroundServiceManagerTest.java index 0f2880e6..b5215c8 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadForegroundServiceManagerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadForegroundServiceManagerTest.java
@@ -12,7 +12,6 @@ import android.app.Notification; import android.content.Context; -import android.os.Looper; import android.support.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -21,16 +20,20 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.chromium.base.test.UiThreadTest; import org.chromium.base.test.util.AdvancedMockContext; +import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.Feature; import org.chromium.chrome.browser.notifications.NotificationWrapperBuilderFactory; import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.content_public.browser.test.util.TestThreadUtils; /** * Test for DownloadForegroundServiceManager. */ @RunWith(ChromeJUnit4ClassRunner.class) +@Batch(Batch.UNIT_TESTS) public final class DownloadForegroundServiceManagerTest { private static final int FAKE_DOWNLOAD_1 = 111; private static final int FAKE_DOWNLOAD_2 = 222; @@ -111,23 +114,25 @@ @Before public void setUp() { - Looper.prepare(); + TestThreadUtils.runOnUiThreadBlocking(() -> { + mContext = new AdvancedMockContext(InstrumentationRegistry.getTargetContext()); + mDownloadServiceManager = new MockDownloadForegroundServiceManager(); - mContext = new AdvancedMockContext(InstrumentationRegistry.getTargetContext()); - mDownloadServiceManager = new MockDownloadForegroundServiceManager(); - - mNotification = - NotificationWrapperBuilderFactory - .createNotificationWrapperBuilder(true /* preferCompat */, - ChromeChannelDefinitions.ChannelId.DOWNLOADS) - .setSmallIcon(org.chromium.chrome.R.drawable.ic_file_download_white_24dp) - .setContentTitle(FAKE_NOTIFICATION_CHANNEL) - .setContentText(FAKE_NOTIFICATION_CHANNEL) - .build(); + mNotification = + NotificationWrapperBuilderFactory + .createNotificationWrapperBuilder(true /* preferCompat */, + ChromeChannelDefinitions.ChannelId.DOWNLOADS) + .setSmallIcon( + org.chromium.chrome.R.drawable.ic_file_download_white_24dp) + .setContentTitle(FAKE_NOTIFICATION_CHANNEL) + .setContentText(FAKE_NOTIFICATION_CHANNEL) + .build(); + }); } @Test @SmallTest + @UiThreadTest @Feature({"Download"}) public void testBasicStartAndStop() { // Service starts and stops with addition and removal of one active download. @@ -170,6 +175,7 @@ @Test @SmallTest + @UiThreadTest @Feature({"Download"}) public void testDelayedStartStop() { // Calls to start and stop service. @@ -190,6 +196,7 @@ @Test @SmallTest + @UiThreadTest @Feature({"Download"}) public void testDelayedStartStopStart() { // Calls to start and stop and start service. @@ -219,6 +226,7 @@ @Test @SmallTest + @UiThreadTest @Feature({"Download"}) public void testIsNotificationKilledOrDetached() { // Service starts and is paused, not complete, so notification not killed but is detached. @@ -265,6 +273,7 @@ @Test @SmallTest + @UiThreadTest @Feature({"Download"}) public void testStopInitiallyAndCleanQueue() { // First call is a download being cancelled.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadForegroundServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadForegroundServiceTest.java index 6c31240..27d32443 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadForegroundServiceTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadForegroundServiceTest.java
@@ -21,6 +21,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.Feature; import org.chromium.chrome.browser.notifications.NotificationWrapperBuilderFactory; import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions; @@ -36,6 +37,7 @@ * Test for DownloadForegroundService. */ @RunWith(ChromeJUnit4ClassRunner.class) +@Batch(Batch.UNIT_TESTS) public class DownloadForegroundServiceTest { private static final int FAKE_DOWNLOAD_ID1 = 1; private static final int FAKE_DOWNLOAD_ID2 = 2;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java index 927e522..b7155f2 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
@@ -22,6 +22,7 @@ import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate; import org.chromium.base.test.params.ParameterSet; import org.chromium.base.test.params.ParameterizedRunner; +import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.DisabledTest; import org.chromium.base.test.util.Feature; import org.chromium.chrome.browser.flags.ChromeFeatureList; @@ -32,9 +33,9 @@ import org.chromium.components.offline_items_collection.ContentId; import org.chromium.components.offline_items_collection.LegacyHelpers; import org.chromium.components.offline_items_collection.OfflineItem.Progress; -import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.components.offline_items_collection.OfflineItemProgressUnit; import org.chromium.components.offline_items_collection.PendingState; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import java.util.Arrays; import java.util.List; @@ -47,6 +48,7 @@ @UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class) @Features.DisableFeatures({ChromeFeatureList.DOWNLOAD_NOTIFICATION_BADGE, ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER}) +@Batch(Batch.UNIT_TESTS) public class DownloadNotificationServiceTest { private static final ContentId ID1 = LegacyHelpers.buildLegacyContentId(false, UUID.randomUUID().toString());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java index cd611c9..99b9ca2 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java
@@ -15,13 +15,14 @@ import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Before; -import org.junit.Rule; +import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.Callback; import org.chromium.base.test.util.AdvancedMockContext; +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; @@ -47,9 +48,10 @@ * Tests for OMADownloadHandler class. */ @RunWith(ChromeJUnit4ClassRunner.class) +@Batch(Batch.PER_CLASS) public class OMADownloadHandlerTest { - @Rule - public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); + @ClassRule + public static final ChromeBrowserTestRule sBrowserTestRule = new ChromeBrowserTestRule(); private static final String INSTALL_NOTIFY_URI = "http://test/test";
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 8c95cbd2..a6f2146 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
@@ -12,11 +12,13 @@ import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Before; -import org.junit.Rule; +import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.chromium.base.test.BaseJUnit4ClassRunner; +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; @@ -31,16 +33,21 @@ * Tests of {@link SystemDownloadNotifier}. */ @RunWith(BaseJUnit4ClassRunner.class) +@Batch(Batch.PER_CLASS) public class SystemDownloadNotifierTest { - @Rule - public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); + @ClassRule + public static final ChromeBrowserTestRule sBrowserTestRule = new ChromeBrowserTestRule(); private final SystemDownloadNotifier mSystemDownloadNotifier = new SystemDownloadNotifier(); private MockDownloadNotificationService mMockDownloadNotificationService; + @BeforeClass + public static void beforeClass() { + Looper.prepare(); + } + @Before public void setUp() { - Looper.prepare(); mMockDownloadNotificationService = new MockDownloadNotificationService(); mSystemDownloadNotifier.setDownloadNotificationService(mMockDownloadNotificationService); mSystemDownloadNotifier.setHandler(new Handler(Looper.getMainLooper()));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java index c155daa..a4d2ed7 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
@@ -107,6 +107,7 @@ ((Callback<Bitmap>) invocation.getArgument(2)).onResult(mBitmap); return null; }; + doReturn(false).when(mLocationBarDataProvider).isIncognito(); doAnswer(bitmapAnswer) .when(mSearchEngineLogoUtils) .getSearchEngineLogoFavicon(any(), eq(mResources), any(), any()); @@ -114,7 +115,7 @@ TestThreadUtils.runOnUiThreadBlocking(() -> { mMediator = new StatusMediator(mModel, mResources, mContext, mUrlBarEditingTextStateProvider, - /* isTablet */ false, mMockForceModelViewReconciliationRunnable, null, + /* isTablet */ false, mMockForceModelViewReconciliationRunnable, mLocationBarDataProvider, mPermissionDialogController, mSearchEngineLogoUtils, () -> mTemplateUrlService, () -> mProfile, null); }); @@ -471,7 +472,8 @@ setupSearchEngineLogoForTesting( /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false); - mMediator.onIncognitoStateChanged(true); + doReturn(true).when(mLocationBarDataProvider).isIncognito(); + mMediator.onIncognitoStateChanged(); verify(mMockForceModelViewReconciliationRunnable, times(0)).run(); } @@ -484,8 +486,10 @@ setupSearchEngineLogoForTesting( /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false); - mMediator.onIncognitoStateChanged(true); - mMediator.onIncognitoStateChanged(false); + doReturn(true).when(mLocationBarDataProvider).isIncognito(); + mMediator.onIncognitoStateChanged(); + doReturn(false).when(mLocationBarDataProvider).isIncognito(); + mMediator.onIncognitoStateChanged(); verify(mMockForceModelViewReconciliationRunnable).run(); } @@ -495,9 +499,10 @@ @UiThreadTest public void testIncognitoStateChange_shouldShowStatusIcon() { mMediator.setShowIconsWhenUrlFocused(true); - - mMediator.onIncognitoStateChanged(true); - mMediator.onIncognitoStateChanged(false); + doReturn(true).when(mLocationBarDataProvider).isIncognito(); + mMediator.onIncognitoStateChanged(); + doReturn(false).when(mLocationBarDataProvider).isIncognito(); + mMediator.onIncognitoStateChanged(); verify(mMockForceModelViewReconciliationRunnable, times(0)).run(); }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java index 2f80bc5..eac373d 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java
@@ -82,7 +82,7 @@ TestThreadUtils.runOnUiThreadBlocking(() -> { mMediator = new StatusMediator(mModel, mResources, mContext, mUrlBarEditingTextStateProvider, - /* isTablet */ false, mMockForceModelViewReconciliationRunnable, null, + /* isTablet */ false, mMockForceModelViewReconciliationRunnable, mLocationBarDataProvider, mPermissionDialogController, mSearchEngineLogoUtils, () -> mTemplateUrlService, () -> mProfile, null); });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java index 8bce2ce94..f8767be 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java
@@ -7,14 +7,8 @@ import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.matcher.RootMatchers.isDialog; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.is; - -import static org.chromium.chrome.test.util.ViewUtils.onViewWaiting; - import androidx.test.filters.LargeTest; import org.junit.After; @@ -26,6 +20,7 @@ import org.junit.runner.RunWith; import org.chromium.base.test.util.CommandLineFlags; +import org.chromium.base.test.util.DisableIf; import org.chromium.base.test.util.Feature; import org.chromium.chrome.R; import org.chromium.chrome.browser.flags.ChromeFeatureList; @@ -47,7 +42,6 @@ import org.chromium.components.signin.identitymanager.ConsentLevel; import org.chromium.components.user_prefs.UserPrefs; import org.chromium.content_public.browser.test.util.TestThreadUtils; -import org.chromium.ui.test.util.DisableAnimationsTestRule; /** * Tests for ManageSyncSettings. @@ -58,9 +52,6 @@ @Rule public final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule(); - @Rule - public final DisableAnimationsTestRule sNoAnimationRule = new DisableAnimationsTestRule(); - public final ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); @@ -120,6 +111,7 @@ @Test @LargeTest @Features.EnableFeatures(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY) + @DisableIf.Build(supported_abis_includes = "x86", message = "https://crbug.com/1142481") public void showSignOutDialogBeforeSigningUserOut() { mAccountManagerTestRule.addTestAccountThenSigninAndEnableSync(); final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings(); @@ -128,7 +120,6 @@ GoogleServicesSettings.PREF_ALLOW_SIGNIN); Assert.assertTrue("Chrome Signin should be allowed", allowChromeSignin.isChecked()); - onViewWaiting(allOf(is(googleServicesSettings.getView()), isDisplayed())); onView(withText(R.string.allow_chrome_signin_title)).perform(click()); // Accept the sign out Dialog onView(withText(R.string.continue_button)).inRoot(isDialog()).perform(click());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkInitializationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkInitializationTest.java index d600010..33e846d4 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkInitializationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkInitializationTest.java
@@ -94,7 +94,8 @@ activityWindowAndroid, compositorViewHolderSupplier, tabCreatorManager, tabCreatorSupplier, isPromotableToTabSupplier, statusBarColorController, screenOrientationProvider, notificationManagerProxySupplier, - tabContentManagerSupplier, compositorViewHolderInitializer) -> { + tabContentManagerSupplier, activityTabStartupMetricsTrackerSupplier, + compositorViewHolderInitializer) -> { mTrackingActivityLifecycleDispatcher.init(lifecycleDispatcher); return new ChromeActivityCommonsModule(activity, bottomSheetControllerSupplier, tabModelSelectorSupplier, browserControlsManager, @@ -104,7 +105,8 @@ activityWindowAndroid, compositorViewHolderSupplier, tabCreatorManager, tabCreatorSupplier, isPromotableToTabSupplier, statusBarColorController, screenOrientationProvider, notificationManagerProxySupplier, - tabContentManagerSupplier, compositorViewHolderInitializer); + tabContentManagerSupplier, activityTabStartupMetricsTrackerSupplier, + compositorViewHolderInitializer); }); private final WebApkActivityTestRule mActivityRule = new WebApkActivityTestRule();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineDetectorUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineDetectorUnitTest.java index d9a5889..e894f58 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineDetectorUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineDetectorUnitTest.java
@@ -77,12 +77,12 @@ // Change to online. changeConnectionState(false); - assertEquals(0, mNotificationReceivedByObserver); + assertEquals(1, mNotificationReceivedByObserver); assertFalse(mLastNotificationReceivedIsOffline); // Change to offline. changeConnectionState(true); - assertEquals("Notification received immediately after connection changed to offline", 0, + assertEquals("Notification received immediately after connection changed to offline", 1, mNotificationReceivedByObserver); assertFalse("Notification received immediately after connection changed to offline", mLastNotificationReceivedIsOffline); @@ -92,14 +92,14 @@ advanceTimeByMs(STATUS_INDICATOR_WAIT_ON_SWITCH_ONLINE_TO_OFFLINE_DEFAULT_DURATION_MS); captor.getValue().run(); - assertEquals("Notification count not updated after connection changed to offline", 1, + assertEquals("Notification count not updated after connection changed to offline", 2, mNotificationReceivedByObserver); assertTrue("Notification not received after connection changed to offline", mLastNotificationReceivedIsOffline); // Change to online. changeConnectionState(false); - assertEquals("Notification count not updated after connection changed to online", 2, + assertEquals("Notification count not updated after connection changed to online", 3, mNotificationReceivedByObserver); assertFalse("Notification not received after connection changed to online", mLastNotificationReceivedIsOffline); @@ -107,7 +107,7 @@ // Change to online again. It should not trigger a notification. changeConnectionState(false); assertEquals( - "Extra notification received even though there is no change in connection state", 2, + "Extra notification received even though there is no change in connection state", 3, mNotificationReceivedByObserver); assertFalse( "Extra notification received even though there is no change in connection state", @@ -205,7 +205,7 @@ advanceTimeByMs(STATUS_INDICATOR_WAIT_ON_OFFLINE_DURATION_MS); captor.getValue().run(); - assertEquals("Extra notification received even though connection is still online", 0, + assertEquals("Extra notification received even though connection is still online", 1, mNotificationReceivedByObserver); assertFalse("Connection is reported as online when it's offline", mLastNotificationReceivedIsOffline); @@ -224,10 +224,10 @@ // Change to online. changeConnectionState(false); - assertEquals(0, mNotificationReceivedByObserver); + assertEquals(1, mNotificationReceivedByObserver); assertFalse(mLastNotificationReceivedIsOffline); - assertEquals("Duplicate notification received after connection changed to online", 0, + assertEquals("Duplicate notification received after connection changed to online", 1, mNotificationReceivedByObserver); assertFalse("Duplicate notification received after connection changed to online.", mLastNotificationReceivedIsOffline); @@ -239,7 +239,7 @@ eq(STATUS_INDICATOR_WAIT_ON_SWITCH_ONLINE_TO_OFFLINE_DEFAULT_DURATION_MS)); advanceTimeByMs(STATUS_INDICATOR_WAIT_ON_SWITCH_ONLINE_TO_OFFLINE_DEFAULT_DURATION_MS); captor.getValue().run(); - assertEquals("Notification not received even though connection is now offline", 1, + assertEquals("Notification not received even though connection is now offline", 2, mNotificationReceivedByObserver); assertTrue("Notification not received even though connection is now offline", mLastNotificationReceivedIsOffline); @@ -256,7 +256,7 @@ // immediately. changeApplicationStateToBackground(false); captor.getValue().run(); - assertEquals("Notification not received even though connection is now online", 2, + assertEquals("Notification not received even though connection is now online", 3, mNotificationReceivedByObserver); assertFalse("Notification not received even though connection is now online", mLastNotificationReceivedIsOffline); @@ -274,12 +274,12 @@ // Change to online. changeConnectionState(false); - assertEquals(0, mNotificationReceivedByObserver); + assertEquals(1, mNotificationReceivedByObserver); assertFalse(mLastNotificationReceivedIsOffline); // Change to offline. changeConnectionState(true); - assertEquals("Notification received immediately after connection changed to offline", 0, + assertEquals("Notification received immediately after connection changed to offline", 1, mNotificationReceivedByObserver); assertFalse("Notification received immediately after connection changed to offline.", mLastNotificationReceivedIsOffline); @@ -289,14 +289,14 @@ advanceTimeByMs( STATUS_INDICATOR_WAIT_ON_SWITCH_ONLINE_TO_OFFLINE_DEFAULT_DURATION_MS - 1000L); - assertEquals("Notification received soon after connection changed to offline", 0, + assertEquals("Notification received soon after connection changed to offline", 1, mNotificationReceivedByObserver); assertFalse("Notification received soon after connection changed to offline", mLastNotificationReceivedIsOffline); // Change to online. changeConnectionState(false); - assertEquals("Extra notification received after connection changed to online", 0, + assertEquals("Extra notification received after connection changed to online", 1, mNotificationReceivedByObserver); assertFalse("Connection is reported as offline when it's online", mLastNotificationReceivedIsOffline); @@ -306,7 +306,7 @@ // since the connection is now online. advanceTimeByMs(1000L); captor.getValue().run(); - assertEquals("Extra notification received even though connection is still online", 0, + assertEquals("Extra notification received even though connection is still online", 1, mNotificationReceivedByObserver); assertFalse("Connection is reported as offline when it's online", mLastNotificationReceivedIsOffline); @@ -321,10 +321,10 @@ changeApplicationStateToBackground(false); // Change to online. changeConnectionState(false); - assertEquals(0, mNotificationReceivedByObserver); + assertEquals(1, mNotificationReceivedByObserver); assertFalse(mLastNotificationReceivedIsOffline); - assertEquals("Notification received immediately after connection changed to online", 0, + assertEquals("Notification received immediately after connection changed to online", 1, mNotificationReceivedByObserver); assertFalse("Notification received immediately after connection changed to online.", mLastNotificationReceivedIsOffline); @@ -341,7 +341,7 @@ verify(mHandler).postDelayed(captor.capture(), eq(STATUS_INDICATOR_WAIT_ON_SWITCH_ONLINE_TO_OFFLINE_DEFAULT_DURATION_MS)); - assertEquals("Extra notification received even though device just changed to offline", 0, + assertEquals("Extra notification received even though device just changed to offline", 1, mNotificationReceivedByObserver); assertFalse("Extra notification received even though device just changed to offline", mLastNotificationReceivedIsOffline); @@ -350,7 +350,7 @@ advanceTimeByMs(STATUS_INDICATOR_WAIT_ON_SWITCH_ONLINE_TO_OFFLINE_DEFAULT_DURATION_MS - STATUS_INDICATOR_WAIT_ON_OFFLINE_DURATION_MS); captor.getValue().run(); - assertEquals("Expected notification when app has been offline for long", 1, + assertEquals("Expected notification when app has been offline for long", 2, mNotificationReceivedByObserver); assertTrue("Expected notification when app has been offline for long", mLastNotificationReceivedIsOffline); @@ -367,10 +367,10 @@ changeApplicationStateToBackground(false); // Change to online. changeConnectionState(false); - assertEquals(0, mNotificationReceivedByObserver); + assertEquals(1, mNotificationReceivedByObserver); assertFalse(mLastNotificationReceivedIsOffline); - assertEquals("Notification received immediately after connection changed to online", 0, + assertEquals("Notification received immediately after connection changed to online", 1, mNotificationReceivedByObserver); assertFalse("Notification received immediately after connection changed to online.", mLastNotificationReceivedIsOffline); @@ -387,7 +387,7 @@ verify(mHandler).postDelayed(captor.capture(), eq(STATUS_INDICATOR_WAIT_ON_SWITCH_ONLINE_TO_OFFLINE_DEFAULT_DURATION_MS)); - assertEquals("Extra notification received even though device just changed to offline", 0, + assertEquals("Extra notification received even though device just changed to offline", 1, mNotificationReceivedByObserver); assertFalse("Extra notification received even though device just changed to offline", mLastNotificationReceivedIsOffline); @@ -401,7 +401,7 @@ advanceTimeByMs(STATUS_INDICATOR_WAIT_ON_SWITCH_ONLINE_TO_OFFLINE_DEFAULT_DURATION_MS - STATUS_INDICATOR_WAIT_ON_OFFLINE_DURATION_MS); captor.getValue().run(); - assertEquals("Extra notification received even though device is now online", 0, + assertEquals("Extra notification received even though device is now online", 1, mNotificationReceivedByObserver); assertFalse("Extra notification received even though device is now online", mLastNotificationReceivedIsOffline);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerV2UnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerV2UnitTest.java index 3f453ac..b05ccc9 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerV2UnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerV2UnitTest.java
@@ -4,6 +4,7 @@ package org.chromium.chrome.browser.offlinepages.indicator; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -27,9 +28,11 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; import org.chromium.base.TimeUtils; import org.chromium.base.TimeUtilsJni; +import org.chromium.base.metrics.test.ShadowRecordHistogram; import org.chromium.base.supplier.ObservableSupplierImpl; import org.chromium.base.supplier.Supplier; import org.chromium.base.test.BaseRobolectricTestRunner; @@ -42,6 +45,7 @@ * Unit tests for {@link OfflineIndicatorControllerV2}. */ @RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE, shadows = {ShadowRecordHistogram.class}) public class OfflineIndicatorControllerV2UnitTest { @Mock private Context mContext; @@ -65,6 +69,32 @@ private OfflineIndicatorControllerV2 mController; private long mElapsedTimeMs; + /** + * Fake of OfflineIndicatorControllerV2.Clock used to test metrics that rely on the wall time. + */ + public static class FakeClock implements OfflineIndicatorControllerV2.Clock { + private long mCurrentTimeMillis; + + public FakeClock() { + mCurrentTimeMillis = 0; + } + + @Override + public long currentTimeMillis() { + return mCurrentTimeMillis; + } + + public void setCurrentTimeMillis(long currentTimeMillis) { + mCurrentTimeMillis = currentTimeMillis; + } + + public void advanceCurrentTimeMillis(long millis) { + mCurrentTimeMillis += millis; + } + } + + private FakeClock mFakeClock; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -80,6 +110,11 @@ TimeUtilsJni.TEST_HOOKS.setInstanceForTesting(mTimeUtils); when(mTimeUtils.getTimeTicksNowUs()).thenReturn(0L); + mFakeClock = new FakeClock(); + OfflineIndicatorControllerV2.setClockForTesting(mFakeClock); + + ShadowRecordHistogram.reset(); + mIsUrlBarFocusedSupplier.set(false); OfflineDetector.setMockConnectivityDetector(mConnectivityDetector); OfflineIndicatorControllerV2.setMockOfflineDetector(mOfflineDetector); @@ -292,6 +327,94 @@ verify(mStatusIndicator, times(2)).show(eq("Offline"), any(), anyInt(), anyInt(), anyInt()); } + /** + * Tests that samples are recorded as expected to the OfflineIndicator.ShownDurationV2 + * histogram when switching between offline and online states. + */ + @Test + public void testPersistedMetrics_ChangeConnectionState() { + // Simulate the connection state going from online to offline then back to online. + changeConnectionState(true); + advanceTimeByMs(10000); + changeConnectionState(false); + + // Check that the correct sample is recorded to OfflineIndicator.ShownDurationV2. + assertEquals(1, + ShadowRecordHistogram.getHistogramTotalCountForTesting( + OfflineIndicatorControllerV2.OFFLINE_INDICATOR_SHOWN_DURATION_V2)); + assertEquals(1, + ShadowRecordHistogram.getHistogramValueCountForTesting( + OfflineIndicatorControllerV2.OFFLINE_INDICATOR_SHOWN_DURATION_V2, 10000)); + } + + /** + * Tests that samples are recorded as expected to the OfflineIndicator.ShownDurationV2 histogram + * in the case where Chrome is killed while offline, and is still offline the next time Chrome + * starts up. + */ + @Test + public void testPersistedMetrics_PersistMetricsAcrossSessionsAndStartUpOffline() { + // Simulate the system going offline. + changeConnectionState(true); + advanceTimeByMs(20000); + + // Simulate Chrome being killed. + mController = null; + advanceTimeByMs(30000); + + // Simulate Chrome starting up again by creating a new instance of the controller. + mController = new OfflineIndicatorControllerV2(mContext, mStatusIndicator, + mIsUrlBarFocusedSupplier, mCanAnimateNativeBrowserControls); + mController.setHandlerForTesting(mHandler); + + // Simulate that the system is still offline upon start up. + changeConnectionState(true); + + // Simulate Chrome going back online. + advanceTimeByMs(40000); + changeConnectionState(false); + + // Check that the correct sample is recorded to OfflineIndicator.ShownDurationV2. + assertEquals(1, + ShadowRecordHistogram.getHistogramTotalCountForTesting( + OfflineIndicatorControllerV2.OFFLINE_INDICATOR_SHOWN_DURATION_V2)); + assertEquals(1, + ShadowRecordHistogram.getHistogramValueCountForTesting( + OfflineIndicatorControllerV2.OFFLINE_INDICATOR_SHOWN_DURATION_V2, 90000)); + } + + /** + * Tests that samples are recorded as expected to the OfflineIndicator.ShownDurationV2 histogram + * in the case where Chrome is killed while offline, and is online the next time Chrome starts + * up. + */ + @Test + public void testPersistedMetrics_PersistMetricsAcrossSessionsAndStartUpOnline() { + // Simulate the system going offline. + changeConnectionState(true); + advanceTimeByMs(20000); + + // Simulate Chrome being killed. + mController = null; + advanceTimeByMs(30000); + + // Simulate Chrome starting up by creating a new instance of the controller. + mController = new OfflineIndicatorControllerV2(mContext, mStatusIndicator, + mIsUrlBarFocusedSupplier, mCanAnimateNativeBrowserControls); + mController.setHandlerForTesting(mHandler); + + // Simulate that the system is online when it starts up. + changeConnectionState(false); + + // Check that the correct sample is recorded to OfflineIndicator.ShownDurationV2. + assertEquals(1, + ShadowRecordHistogram.getHistogramTotalCountForTesting( + OfflineIndicatorControllerV2.OFFLINE_INDICATOR_SHOWN_DURATION_V2)); + assertEquals(1, + ShadowRecordHistogram.getHistogramValueCountForTesting( + OfflineIndicatorControllerV2.OFFLINE_INDICATOR_SHOWN_DURATION_V2, 50000)); + } + private void changeConnectionState(boolean offline) { final int state = offline ? ConnectionState.NO_INTERNET : ConnectionState.VALIDATED; when(mOfflineDetector.isConnectionStateOffline()).thenReturn(offline); @@ -301,5 +424,7 @@ private void advanceTimeByMs(long delta) { mElapsedTimeMs += delta; setMockElapsedTimeSupplier(() -> mElapsedTimeMs); + + mFakeClock.advanceCurrentTimeMillis(delta); } }
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp index 692dc93..e9078de 100644 --- a/chrome/app/os_settings_strings.grdp +++ b/chrome/app/os_settings_strings.grdp
@@ -1295,6 +1295,27 @@ </message> <!-- Used by both Crostini and Plugin VM --> + <message name="IDS_SETTINGS_GUEST_OS_SHARED_PATHS" desc="Label for managing shared folders for Parallels."> + Manage shared folders + </message> + <message name="IDS_SETTINGS_GUEST_OS_SHARED_PATHS_LIST_HEADING" desc="Label for list of shared folders."> + Shared folders + </message> + <message name="IDS_SETTINGS_GUEST_OS_SHARED_PATHS_INSTRUCTIONS_REMOVE" desc="Instructions for removing shared folders in Parallels."> + Removing folders will stop sharing but will not delete files. + </message> + <message name="IDS_SETTINGS_GUEST_OS_SHARED_PATHS_STOP_SHARING" desc="Tooltip to show when hovering on the remove icon for a Parallels shared folder."> + Stop sharing + </message> + <message name="IDS_SETTINGS_GUEST_OS_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE" desc="Title of the error dialog shown to a user when unsharing a Parallels shared folder fails."> + Unshare failed + </message> + <message name="IDS_SETTINGS_GUEST_OS_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN" desc="Button text in the dialog shown when unsharing a Parallels shared folder fails. Pressing this button will attempt to unshare the folder again."> + Try again + </message> + <message name="IDS_SETTINGS_GUEST_OS_SHARED_PATHS_LIST_EMPTY_MESSAGE" desc="Message shown when the user has not yet shared any folders in Parallels."> + Shared folders will appear here + </message> <message name="IDS_SETTINGS_GUEST_OS_SHARED_USB_DEVICES_LABEL" desc="Label for managing shared USB devices."> Manage USB devices </message> @@ -1318,36 +1339,15 @@ <message name="IDS_SETTINGS_CROSTINI_LABEL" desc="The text associated with the primary section setting. This contains settings for a Linux container running in a virtual machine for use by software developers."> Linux development environment (Beta) </message> - <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS" desc="Label for managing shared folders in Crostini."> - Manage shared folders - </message> - <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS_LIST_HEADING" desc="Label for list of shared folders."> - Shared folders - </message> <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS_INSTRUCTIONS_LOCATE" desc="Instructions for how to locate shared folders in Crostini."> Shared folders are available in Linux at <ph name="BASE_DIR">$1<ex>/mnt/chromeos</ex></ph>. </message> <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS_INSTRUCTIONS_ADD" desc="Instructions for how to add shared folders in Crostini."> To share, right-click on a folder in Files app, then select "Share with Linux". </message> - <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS_INSTRUCTIONS_REMOVE" desc="Instructions for removing shared folders in Crostini."> - Removing folders from here will stop sharing but will not delete files. - </message> - <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_SHARING" desc="Tooltip to show when hovering on the remove icon for a crostini shared folder."> - Remove sharing - </message> <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_DIALOG_MESSAGE" desc="Message to show user when unsharing a crostini shared folder fails."> Couldn't unshare because an application is using this folder. The folder will be unshared when Linux is next shut down. </message> - <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE" desc="Title of the error dialog shown to a user when unsharing a crostini shared folder fails."> - Unshare failed - </message> - <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN" desc="Button text in the dialog shown when unsharing a crostini shared folder fails. Pressing this button will attempt to unshare the folder again."> - Try again - </message> - <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS_LIST_EMPTY_MESSAGE" desc="Message shown when the user has not yet shared any folders in Crostini."> - Shared folders will appear here - </message> <message name="IDS_SETTINGS_CROSTINI_EXPORT_IMPORT_TITLE" desc="Title for crostini container export and imoprt (backup and restore) section"> Backup & restore </message> @@ -3373,36 +3373,15 @@ </message> <!-- Apps > Manage your apps > Parallels > Shared folders --> - <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS" desc="Label for managing shared folders for Parallels."> - Manage shared folders - </message> - <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_HEADING" desc="Label for list of shared folders."> - Shared folders - </message> <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_LOCATE" desc="Instructions for how to locate shared folders in Parallels."> Shared folders are available in Windows at <ph name="BASE_DIR">$1<ex>Network › ChromeOS</ex></ph>. </message> <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_ADD" desc="Instructions for how to add shared folders in Parallels."> To share, right-click on a folder in Files app, then select "Share with Parallels Desktop". </message> - <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_REMOVE" desc="Instructions for removing shared folders in Parallels."> - Removing folders will stop sharing but will not delete files. - </message> - <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING" desc="Tooltip to show when hovering on the remove icon for a Parallels shared folder."> - Stop sharing - </message> <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_MESSAGE" desc="Message to show user when unsharing a Parallels shared folder fails. Do not translate 'Parallels Desktop'"> Couldn't unshare because an application is using this folder. The folder will be unshared when Parallels Desktop is next shut down. </message> - <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE" desc="Title of the error dialog shown to a user when unsharing a Parallels shared folder fails."> - Unshare failed - </message> - <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN" desc="Button text in the dialog shown when unsharing a Parallels shared folder fails. Pressing this button will attempt to unshare the folder again."> - Try again - </message> - <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_EMPTY_MESSAGE" desc="Message shown when the user has not yet shared any folders in Parallels."> - Shared folders will appear here - </message> <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_USB_DEVICES_DESCRIPTION" desc="Description for managing shared USB devices. Do not translate 'Parallels Desktop'."> Give Parallels Desktop permission to access USB devices. Parallels Desktop won't remember a USB device after it's removed. </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_EMPTY_MESSAGE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_EMPTY_MESSAGE.png.sha1 deleted file mode 100644 index bc1229e..0000000 --- a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_EMPTY_MESSAGE.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -7338b60167490936082e4a72214fda0a68a16807 \ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE.png.sha1 deleted file mode 100644 index 04f962d..0000000 --- a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -ec0fdd89ab5d4afc79c7e65d463ecece4925ac68 \ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS.png.sha1 similarity index 100% copy from chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING.png.sha1 copy to chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS.png.sha1
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_REMOVE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_INSTRUCTIONS_REMOVE.png.sha1 similarity index 100% rename from chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_REMOVE.png.sha1 rename to chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_INSTRUCTIONS_REMOVE.png.sha1
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_LIST_EMPTY_MESSAGE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_LIST_EMPTY_MESSAGE.png.sha1 similarity index 100% rename from chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_LIST_EMPTY_MESSAGE.png.sha1 rename to chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_LIST_EMPTY_MESSAGE.png.sha1
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_LIST_HEADING.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_LIST_HEADING.png.sha1 similarity index 100% rename from chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_LIST_HEADING.png.sha1 rename to chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_LIST_HEADING.png.sha1
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE.png.sha1 similarity index 100% rename from chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE.png.sha1 rename to chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE.png.sha1
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN.png.sha1 similarity index 100% rename from chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN.png.sha1 rename to chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN.png.sha1
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_STOP_SHARING.png.sha1 similarity index 100% rename from chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING.png.sha1 rename to chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GUEST_OS_SHARED_PATHS_STOP_SHARING.png.sha1
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_INSTRUCTIONS_REMOVE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_INSTRUCTIONS_REMOVE.png.sha1 deleted file mode 100644 index 41b5b3f5..0000000 --- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_INSTRUCTIONS_REMOVE.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -5ac77392ae93168b2861bf4fda2359288f40df60 \ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN.png.sha1 deleted file mode 100644 index a0047bd..0000000 --- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -f735083d7a1a1439fb455dbd6770cd16b5628b21 \ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_SHARING.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_SHARING.png.sha1 deleted file mode 100644 index 456acee..0000000 --- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_SHARING.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -4dcc02b4b67dadb033eadd0f51e34946d4b582b4 \ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 931d6170..86edc9e 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -2304,7 +2304,6 @@ "//media/mojo/services", "//media/webrtc", "//mojo/core/embedder", - "//mojo/core/embedder:features", "//mojo/public/cpp/bindings", "//net", "//net:extras",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS index 3ae0cc75..3ac0950 100644 --- a/chrome/browser/DEPS +++ b/chrome/browser/DEPS
@@ -533,7 +533,4 @@ "chrome_content_browser_client\.cc" : [ "+content/public/browser/tts_controller.h", ], - "about_flags\.cc" : [ - "+mojo/core/embedder/features.h", - ] }
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index fb6f0b0..09b1c41 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -157,7 +157,6 @@ #include "media/media_buildflags.h" #include "media/midi/midi_switches.h" #include "media/webrtc/webrtc_switches.h" -#include "mojo/core/embedder/features.h" #include "net/base/features.h" #include "net/net_buildflags.h" #include "net/nqe/effective_connection_type.h" @@ -3237,13 +3236,6 @@ FEATURE_VALUE_TYPE(performance_manager::features::kDynamicTcmallocTuning)}, #endif // BUILDFLAG(USE_TCMALLOC) #endif // BUILDFLAG(IS_CHROMEOS_ASH) -#if (defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_ANDROID)) && \ - !defined(OS_NACL) - {"mojo-linux-sharedmem", flag_descriptions::kMojoLinuxChannelSharedMemName, - flag_descriptions::kMojoLinuxChannelSharedMemDescription, - kOsCrOS | kOsLinux | kOsAndroid, - FEATURE_VALUE_TYPE(mojo::core::kMojoLinuxChannelSharedMem)}, -#endif #if defined(OS_ANDROID) {"enable-site-isolation-for-password-sites", flag_descriptions::kSiteIsolationForPasswordSitesName, @@ -6100,6 +6092,9 @@ {"exo-gamepad-vibration", flag_descriptions::kExoGamepadVibrationName, flag_descriptions::kExoGamepadVibrationDescription, kOsCrOS, FEATURE_VALUE_TYPE(chromeos::features::kGamepadVibration)}, + {"exo-lock-notification", flag_descriptions::kExoLockNotificationName, + flag_descriptions::kExoLockNotificationDescription, kOsCrOS, + FEATURE_VALUE_TYPE(chromeos::features::kExoLockNotification)}, {"exo-ordinal-motion", flag_descriptions::kExoOrdinalMotionName, flag_descriptions::kExoOrdinalMotionDescription, kOsCrOS, FEATURE_VALUE_TYPE(chromeos::features::kExoOrdinalMotion)}, @@ -7042,6 +7037,14 @@ FEATURE_VALUE_TYPE(ash::features::kWallpaperWebUI)}, #endif // BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS_ASH) + // TODO(b/177462291): make flag available on LaCrOS. + {"enable-vaapi-av1-decode-acceleration", + flag_descriptions::kVaapiAV1DecoderName, + flag_descriptions::kVaapiAV1DecoderDescription, kOsCrOS, + FEATURE_VALUE_TYPE(media::kVaapiAV1Decoder)}, +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + // NOTE: Adding a new flag requires adding a corresponding entry to enum // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/accessibility/caption_controller.cc b/chrome/browser/accessibility/caption_controller.cc index bc284a0f..61c7bfc 100644 --- a/chrome/browser/accessibility/caption_controller.cc +++ b/chrome/browser/accessibility/caption_controller.cc
@@ -108,7 +108,8 @@ // which case the UI will construct prematurely. // TODO(crbug.com/1160272): Check whether SODA has downloaded without // blocking the process. - if (speech::SodaInstaller::GetInstance()->IsSodaRegistered()) { + if (!base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption) || + speech::SodaInstaller::GetInstance()->IsSodaRegistered()) { UpdateUIEnabled(); } else { // Register SODA component and download speech model.
diff --git a/chrome/browser/accessibility/soda_installer_impl.cc b/chrome/browser/accessibility/soda_installer_impl.cc index 9a62105..44676d3e 100644 --- a/chrome/browser/accessibility/soda_installer_impl.cc +++ b/chrome/browser/accessibility/soda_installer_impl.cc
@@ -97,8 +97,7 @@ } bool SodaInstallerImpl::IsSodaRegistered() { - if (!base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption)) - return true; + DCHECK(base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption)); std::vector<std::string> component_ids = g_browser_process->component_updater()->GetComponentIDs(); const bool has_soda = base::Contains(
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.cc b/chrome/browser/android/vr/arcore_device/fake_arcore.cc index 58c186d3..c90bcec 100644 --- a/chrome/browser/android/vr/arcore_device/fake_arcore.cc +++ b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
@@ -27,14 +27,26 @@ required_features, const std::unordered_set<device::mojom::XRSessionFeature>& optional_features, - const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images) { + const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images, + base::Optional<ArCore::DepthSensingConfiguration> depth_sensing_config) { DCHECK(IsOnGlThread()); std::unordered_set<device::mojom::XRSessionFeature> enabled_features; enabled_features.insert(required_features.begin(), required_features.end()); enabled_features.insert(optional_features.begin(), optional_features.end()); - return ArCore::InitializeResult(enabled_features); + // Fake device does not support depth for now: + if (base::Contains(required_features, + device::mojom::XRSessionFeature::DEPTH)) { + return base::nullopt; + } + + if (base::Contains(optional_features, + device::mojom::XRSessionFeature::DEPTH)) { + enabled_features.erase(device::mojom::XRSessionFeature::DEPTH); + } + + return ArCore::InitializeResult(enabled_features, base::nullopt); } void FakeArCore::SetDisplayGeometry(
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.h b/chrome/browser/android/vr/arcore_device/fake_arcore.h index b771bb0..66e2ab9 100644 --- a/chrome/browser/android/vr/arcore_device/fake_arcore.h +++ b/chrome/browser/android/vr/arcore_device/fake_arcore.h
@@ -28,7 +28,8 @@ required_features, const std::unordered_set<device::mojom::XRSessionFeature>& optional_features, - const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images) + const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images, + base::Optional<ArCore::DepthSensingConfiguration> depth_sensing_config) override; MinMaxRange GetTargetFramerateRange() override; void SetCameraTexture(uint32_t texture) override;
diff --git a/chrome/browser/ash/README.md b/chrome/browser/ash/README.md index 20cdbe4..82ab04e 100644 --- a/chrome/browser/ash/README.md +++ b/chrome/browser/ash/README.md
@@ -4,6 +4,14 @@ This directory should contain non-UI Chrome OS specific code that has `chrome/browser` dependencies. +The code in this directory should live in namespace ash. While code in +//chrome is not supposed to be in any namespace, //chrome/browser/ash is +technically part of the ash binary. The fact that it lives in //chrome/browser +instead of in //ash is because top level product directories shouldn't be +depended on by any other directory. In the future, when some of the +dependencies from //chrome/browser/ash to //chrome/browser are sorted out, +some of this code will move to //ash. + As of January 2021, code from [`chrome/browser/chromeos`](/chrome/browser/chromeos/README.md) is migrating into this directory, as part of the [Lacros project](/docs/lacros.md).
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc index 86043ac..7a01fff7 100644 --- a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc +++ b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
@@ -146,11 +146,12 @@ void LoggedInSpokenFeedbackTest::EnableChromeVox() { // Test setup. - // Enable ChromeVox, wait for something to be spoken, and disable earcons. + // Enable ChromeVox, disable earcons and wait for key mappings to be fetched. ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled()); // TODO(accessibility): fix console error/warnings and insantiate // |console_observer_| here. + // Load ChromeVox and block until it's fully loaded. AccessibilityManager::Get()->EnableSpokenFeedback(true); base::RunLoop loop; ExtensionLoadWaiterOneShot waiter; @@ -158,8 +159,10 @@ loop.QuitClosure()); loop.Run(); + DisableEarcons(); + // Keyboard mappings are async loaded, so we need to wait until they are fully - // loaded to start testing. + // fetched to start testing. std::string script = R"JS( function waitForKeyMapLoad() { @@ -177,9 +180,6 @@ browser()->profile(), extension_misc::kChromeVoxExtensionId, script, extensions::browsertest_util::ScriptUserActivation::kDontActivate); ASSERT_EQ(result, "ok"); - - sm_.ExpectSpeechPattern("*"); - sm_.Call([this]() { DisableEarcons(); }); } // Flaky test, crbug.com/1081563 @@ -1058,7 +1058,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_SmartStickyMode) { +IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, SmartStickyMode) { EnableChromeVox(); sm_.Call([this]() { ui_test_utils::NavigateToURL(browser(),
diff --git a/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller.cc b/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller.cc index 7a0ed993..30741a11 100644 --- a/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller.cc +++ b/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller.cc
@@ -17,7 +17,9 @@ #include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_service.h" -namespace chromeos { +namespace ash { + +namespace edu_coexistence = ::chromeos::edu_coexistence; void EduCoexistenceConsentInvalidationController::RegisterProfilePrefs( PrefRegistrySimple* registry) { @@ -45,7 +47,7 @@ device_account_id_(device_account_id) { DCHECK(profile_); DCHECK(profile_->IsChild()); - DCHECK(chromeos::IsAccountManagerAvailable(profile_)); + DCHECK(IsAccountManagerAvailable(profile_)); } EduCoexistenceConsentInvalidationController:: @@ -158,4 +160,4 @@ } } -} // namespace chromeos +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller.h b/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller.h index 026ff8e..150ce53 100644 --- a/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller.h +++ b/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller.h
@@ -10,16 +10,17 @@ #include "base/callback.h" #include "base/memory/weak_ptr.h" +// TODO(https://crbug.com/1164001): move to forward declaration when migrated to +// ash/components/. +#include "chromeos/components/account_manager/account_manager.h" #include "components/account_id/account_id.h" #include "components/account_manager_core/account.h" #include "components/prefs/pref_change_registrar.h" -class Profile; class PrefRegistrySimple; +class Profile; -namespace chromeos { - -class AccountManager; +namespace ash { // Listens to changes to chromeos::prefs::kEduCoexistenceToSVersion policy // preference and invalidates secondary edu accounts with outdated terms of @@ -66,6 +67,6 @@ weak_factory_{this}; }; -} // namespace chromeos +} // namespace ash #endif // CHROME_BROWSER_ASH_ACCOUNT_MANAGER_ACCOUNT_MANAGER_EDU_COEXISTENCE_CONTROLLER_H_
diff --git a/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller_unittest.cc b/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller_unittest.cc index c3661a7..b21f3cf1 100644 --- a/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller_unittest.cc +++ b/chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller_unittest.cc
@@ -26,10 +26,12 @@ #include "services/network/test/test_url_loader_factory.h" #include "testing/gtest/include/gtest/gtest.h" -namespace chromeos { +namespace ash { namespace { +namespace edu_coexistence = ::chromeos::edu_coexistence; + constexpr char kValidToken[] = "valid-token"; constexpr char kPrimaryAccount[] = "primaryaccount@gmail.com"; @@ -242,4 +244,4 @@ "7"); } -} // namespace chromeos +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_manager_migrator.cc b/chrome/browser/ash/account_manager/account_manager_migrator.cc index a6c3981b..f9dbbe8 100644 --- a/chrome/browser/ash/account_manager/account_manager_migrator.cc +++ b/chrome/browser/ash/account_manager/account_manager_migrator.cc
@@ -47,7 +47,7 @@ #include "components/webdata/common/web_data_service_consumer.h" #include "google_apis/gaia/gaia_auth_util.h" -namespace chromeos { +namespace ash { namespace { @@ -497,7 +497,7 @@ void AccountManagerMigrator::Start() { DVLOG(1) << "AccountManagerMigrator::Start"; - if (!chromeos::IsAccountManagerAvailable(profile_)) + if (!IsAccountManagerAvailable(profile_)) return; if (migration_runner_ && (migration_runner_->GetStatus() == @@ -676,4 +676,4 @@ return new AccountManagerMigrator(Profile::FromBrowserContext(context)); } -} // namespace chromeos +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_manager_migrator.h b/chrome/browser/ash/account_manager/account_manager_migrator.h index 3c28dbd..1866e3a 100644 --- a/chrome/browser/ash/account_manager/account_manager_migrator.h +++ b/chrome/browser/ash/account_manager/account_manager_migrator.h
@@ -21,7 +21,7 @@ class NoDestructor; } // namespace base -namespace chromeos { +namespace ash { class AccountManagerMigrator : public KeyedService { public: @@ -94,6 +94,6 @@ DISALLOW_COPY_AND_ASSIGN(AccountManagerMigratorFactory); }; -} // namespace chromeos +} // namespace ash #endif // CHROME_BROWSER_ASH_ACCOUNT_MANAGER_ACCOUNT_MANAGER_MIGRATOR_H_
diff --git a/chrome/browser/ash/account_manager/account_manager_policy_controller.cc b/chrome/browser/ash/account_manager/account_manager_policy_controller.cc index db105b1b..01135e97 100644 --- a/chrome/browser/ash/account_manager/account_manager_policy_controller.cc +++ b/chrome/browser/ash/account_manager/account_manager_policy_controller.cc
@@ -14,10 +14,11 @@ #include "chrome/browser/chromeos/child_accounts/secondary_account_consent_logger.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/supervised_user/supervised_user_features.h" +#include "chromeos/components/account_manager/account_manager.h" #include "chromeos/constants/chromeos_pref_names.h" #include "components/prefs/pref_service.h" -namespace chromeos { +namespace ash { AccountManagerPolicyController::AccountManagerPolicyController( Profile* profile, @@ -34,7 +35,7 @@ void AccountManagerPolicyController::Start() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (!chromeos::IsAccountManagerAvailable(profile_)) + if (!IsAccountManagerAvailable(profile_)) return; pref_change_registrar_.Init(profile_->GetPrefs()); @@ -46,8 +47,8 @@ // Take any necessary initial action based on the current state of the pref. OnSecondaryAccountsSigninAllowedPrefChanged(); - chromeos::ChildAccountTypeChangedUserData* user_data = - chromeos::ChildAccountTypeChangedUserData::GetForProfile(profile_); + ChildAccountTypeChangedUserData* user_data = + ChildAccountTypeChangedUserData::GetForProfile(profile_); child_account_type_changed_subscription_ = user_data->RegisterCallback(base::BindRepeating( &AccountManagerPolicyController::OnChildAccountTypeChanged, @@ -174,4 +175,4 @@ edu_coexistence_consent_invalidation_controller_.reset(); } -} // namespace chromeos +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_manager_policy_controller.h b/chrome/browser/ash/account_manager/account_manager_policy_controller.h index 276a1145..a12103ed 100644 --- a/chrome/browser/ash/account_manager/account_manager_policy_controller.h +++ b/chrome/browser/ash/account_manager/account_manager_policy_controller.h
@@ -12,6 +12,8 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "chrome/browser/ash/account_manager/child_account_type_changed_user_data.h" +// TODO(https://crbug.com/1164001): move to forward declaration when migrated to +// ash/components/. #include "chromeos/components/account_manager/account_manager.h" #include "components/account_id/account_id.h" #include "components/account_manager_core/account.h" @@ -21,9 +23,7 @@ class Profile; -namespace chromeos { - -class AccountManager; +namespace ash { class EduCoexistenceConsentInvalidationController; class AccountManagerPolicyController : public KeyedService { @@ -33,7 +33,7 @@ const AccountId& device_account_id); ~AccountManagerPolicyController() override; - // Starts applying the behaviour required by |chromeos::AccountManager| + // Starts applying the behaviour required by |AccountManager| // specific prefs and policies. void Start(); @@ -84,6 +84,6 @@ DISALLOW_COPY_AND_ASSIGN(AccountManagerPolicyController); }; -} // namespace chromeos +} // namespace ash #endif // CHROME_BROWSER_ASH_ACCOUNT_MANAGER_ACCOUNT_MANAGER_POLICY_CONTROLLER_H_
diff --git a/chrome/browser/ash/account_manager/account_manager_policy_controller_browsertest.cc b/chrome/browser/ash/account_manager/account_manager_policy_controller_browsertest.cc index 65e050b5..48ec297 100644 --- a/chrome/browser/ash/account_manager/account_manager_policy_controller_browsertest.cc +++ b/chrome/browser/ash/account_manager/account_manager_policy_controller_browsertest.cc
@@ -22,8 +22,7 @@ #include "content/public/test/browser_test.h" #include "testing/gtest/include/gtest/gtest.h" -namespace chromeos { - +namespace ash { namespace { constexpr char kFakePrimaryUsername[] = "test-primary@example.com"; constexpr char kFakeSecondaryUsername[] = "test-secondary@example.com"; @@ -133,8 +132,7 @@ // (|true|). profile()->GetPrefs()->SetBoolean( chromeos::prefs::kSecondaryGoogleAccountSigninAllowed, true); - chromeos::ChildAccountTypeChangedUserData::GetForProfile(profile())->SetValue( - false); + ChildAccountTypeChangedUserData::GetForProfile(profile())->SetValue(false); // All accounts must be intact. accounts = GetAccountManagerAccounts(); @@ -181,8 +179,7 @@ ASSERT_GT(initial_num_accounts, 1UL); // Disallow secondary account sign-ins. - chromeos::ChildAccountTypeChangedUserData::GetForProfile(profile())->SetValue( - true); + ChildAccountTypeChangedUserData::GetForProfile(profile())->SetValue(true); // Secondary Accounts must be removed. accounts = GetAccountManagerAccounts(); @@ -202,4 +199,4 @@ accounts[0].key.id); } -} // namespace chromeos +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_manager_policy_controller_factory.cc b/chrome/browser/ash/account_manager/account_manager_policy_controller_factory.cc index 3d49ba17..ac58964 100644 --- a/chrome/browser/ash/account_manager/account_manager_policy_controller_factory.cc +++ b/chrome/browser/ash/account_manager/account_manager_policy_controller_factory.cc
@@ -13,7 +13,7 @@ #include "chromeos/components/account_manager/account_manager_factory.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" -namespace chromeos { +namespace ash { // static AccountManagerPolicyController* @@ -63,4 +63,4 @@ return service; } -} // namespace chromeos +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_manager_policy_controller_factory.h b/chrome/browser/ash/account_manager/account_manager_policy_controller_factory.h index e75ff04..fd072710 100644 --- a/chrome/browser/ash/account_manager/account_manager_policy_controller_factory.h +++ b/chrome/browser/ash/account_manager/account_manager_policy_controller_factory.h
@@ -15,8 +15,7 @@ class NoDestructor; } // namespace base -namespace chromeos { - +namespace ash { class AccountManagerPolicyController; class AccountManagerPolicyControllerFactory @@ -42,6 +41,6 @@ DISALLOW_COPY_AND_ASSIGN(AccountManagerPolicyControllerFactory); }; -} // namespace chromeos +} // namespace ash #endif // CHROME_BROWSER_ASH_ACCOUNT_MANAGER_ACCOUNT_MANAGER_POLICY_CONTROLLER_FACTORY_H_
diff --git a/chrome/browser/ash/account_manager/account_manager_ui_impl.cc b/chrome/browser/ash/account_manager/account_manager_ui_impl.cc index 3b26bc1..9b48fe5 100644 --- a/chrome/browser/ash/account_manager/account_manager_ui_impl.cc +++ b/chrome/browser/ash/account_manager/account_manager_ui_impl.cc
@@ -5,7 +5,9 @@ #include "chrome/browser/ash/account_manager/account_manager_ui_impl.h" #include "chrome/browser/ui/webui/signin/inline_login_dialog_chromeos.h" -namespace chromeos { +namespace ash { + +using ::chromeos::InlineLoginDialogChromeOS; AccountManagerUIImpl::AccountManagerUIImpl() = default; AccountManagerUIImpl::~AccountManagerUIImpl() = default; @@ -25,4 +27,4 @@ return InlineLoginDialogChromeOS::IsShown(); } -} // namespace chromeos +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_manager_ui_impl.h b/chrome/browser/ash/account_manager/account_manager_ui_impl.h index 047bc5c..434004d 100644 --- a/chrome/browser/ash/account_manager/account_manager_ui_impl.h +++ b/chrome/browser/ash/account_manager/account_manager_ui_impl.h
@@ -8,7 +8,7 @@ #include "base/callback_forward.h" #include "chromeos/components/account_manager/account_manager_ui.h" -namespace chromeos { +namespace ash { class AccountManagerUIImpl : public AccountManagerUI { public: @@ -25,6 +25,6 @@ bool IsDialogShown() override; }; -} // namespace chromeos +} // namespace ash #endif // CHROME_BROWSER_ASH_ACCOUNT_MANAGER_ACCOUNT_MANAGER_UI_IMPL_H_
diff --git a/chrome/browser/ash/account_manager/account_manager_util.cc b/chrome/browser/ash/account_manager/account_manager_util.cc index 4592886d..b635ae3 100644 --- a/chrome/browser/ash/account_manager/account_manager_util.cc +++ b/chrome/browser/ash/account_manager/account_manager_util.cc
@@ -18,7 +18,7 @@ #include "components/user_manager/user_manager.h" #include "services/network/public/cpp/shared_url_loader_factory.h" -namespace chromeos { +namespace ash { bool IsAccountManagerAvailable(const Profile* const profile) { // Signin Profile does not have any accounts associated with it, @@ -63,4 +63,4 @@ std::move(initialization_callback)); } -} // namespace chromeos +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_manager_util.h b/chrome/browser/ash/account_manager/account_manager_util.h index 0f6284a3..e0b99680 100644 --- a/chrome/browser/ash/account_manager/account_manager_util.h +++ b/chrome/browser/ash/account_manager/account_manager_util.h
@@ -10,7 +10,7 @@ class Profile; -namespace chromeos { +namespace ash { bool IsAccountManagerAvailable(const Profile* const profile); @@ -21,6 +21,6 @@ void InitializeAccountManager(const base::FilePath& cryptohome_root_dir, base::OnceClosure initialization_callback); -} // namespace chromeos +} // namespace ash #endif // CHROME_BROWSER_ASH_ACCOUNT_MANAGER_ACCOUNT_MANAGER_UTIL_H_
diff --git a/chrome/browser/ash/account_manager/account_migration_runner.cc b/chrome/browser/ash/account_manager/account_migration_runner.cc index 10a8c03..50532f0 100644 --- a/chrome/browser/ash/account_manager/account_migration_runner.cc +++ b/chrome/browser/ash/account_manager/account_migration_runner.cc
@@ -10,7 +10,7 @@ #include "base/bind.h" #include "base/metrics/histogram_functions.h" -namespace chromeos { +namespace ash { // Used in histograms. constexpr char kMigrationStepResultMetricName[] = @@ -130,4 +130,4 @@ failed_step_id /* failed_step */}); } -} // namespace chromeos +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_migration_runner.h b/chrome/browser/ash/account_manager/account_migration_runner.h index 39f37c8..73fba57 100644 --- a/chrome/browser/ash/account_manager/account_migration_runner.h +++ b/chrome/browser/ash/account_manager/account_migration_runner.h
@@ -14,7 +14,7 @@ #include "base/memory/weak_ptr.h" #include "base/sequence_checker.h" -namespace chromeos { +namespace ash { // A utility class to run account migrations for |chromeos::AccountManager|. It // enables the specification of a series of async migration |Step|s in a @@ -146,6 +146,6 @@ DISALLOW_COPY_AND_ASSIGN(AccountMigrationRunner); }; -} // namespace chromeos +} // namespace ash #endif // CHROME_BROWSER_ASH_ACCOUNT_MANAGER_ACCOUNT_MIGRATION_RUNNER_H_
diff --git a/chrome/browser/ash/account_manager/account_migration_runner_unittest.cc b/chrome/browser/ash/account_manager/account_migration_runner_unittest.cc index e27ecf4..0ca554e 100644 --- a/chrome/browser/ash/account_manager/account_migration_runner_unittest.cc +++ b/chrome/browser/ash/account_manager/account_migration_runner_unittest.cc
@@ -15,7 +15,7 @@ #include "base/test/task_environment.h" #include "testing/gtest/include/gtest/gtest.h" -namespace chromeos { +namespace ash { namespace { @@ -196,4 +196,4 @@ EXPECT_EQ(2, num_steps_executed_); } -} // namespace chromeos +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/child_account_type_changed_user_data.cc b/chrome/browser/ash/account_manager/child_account_type_changed_user_data.cc index 4e91653..d687062 100644 --- a/chrome/browser/ash/account_manager/child_account_type_changed_user_data.cc +++ b/chrome/browser/ash/account_manager/child_account_type_changed_user_data.cc
@@ -6,7 +6,7 @@ #include "chrome/browser/profiles/profile.h" -namespace chromeos { +namespace ash { namespace { const void* const kChildAccountTypeChangedUserDataUserKey = @@ -47,4 +47,5 @@ const base::RepeatingCallback<void(bool)>& cb) { return callback_list_.Add(cb); } -} // namespace chromeos + +} // namespace ash
diff --git a/chrome/browser/ash/account_manager/child_account_type_changed_user_data.h b/chrome/browser/ash/account_manager/child_account_type_changed_user_data.h index 39bbe05..6b350dde 100644 --- a/chrome/browser/ash/account_manager/child_account_type_changed_user_data.h +++ b/chrome/browser/ash/account_manager/child_account_type_changed_user_data.h
@@ -11,7 +11,8 @@ #include "base/supports_user_data.h" class Profile; -namespace chromeos { + +namespace ash { class ChildAccountTypeChangedUserData : public base::SupportsUserData::Data { public: @@ -36,6 +37,7 @@ bool value_ = false; base::CallbackList<void(bool)> callback_list_; }; -} // namespace chromeos + +} // namespace ash #endif // CHROME_BROWSER_ASH_ACCOUNT_MANAGER_CHILD_ACCOUNT_TYPE_CHANGED_USER_DATA_H_
diff --git a/chrome/browser/banners/app_banner_manager_desktop.h b/chrome/browser/banners/app_banner_manager_desktop.h index 6ece0e4a..0814f2ad 100644 --- a/chrome/browser/banners/app_banner_manager_desktop.h +++ b/chrome/browser/banners/app_banner_manager_desktop.h
@@ -64,6 +64,10 @@ const blink::Manifest::RelatedApplication& related_app) const override; bool IsWebAppConsideredInstalled() const override; + // content::WebContentsObserver override. + void DidFinishLoad(content::RenderFrameHost* render_frame_host, + const GURL& validated_url) override; + // Called when the web app install initiated by a banner has completed. virtual void DidFinishCreatingWebApp(const web_app::AppId& app_id, web_app::InstallResultCode code); @@ -78,10 +82,6 @@ bool ShouldAllowWebAppReplacementInstall() override; void ShowBannerUi(WebappInstallSource install_source) override; - // content::WebContentsObserver override. - void DidFinishLoad(content::RenderFrameHost* render_frame_host, - const GURL& validated_url) override; - // SiteEngagementObserver override. void OnEngagementEvent(content::WebContents* web_contents, const GURL& url,
diff --git a/chrome/browser/banners/test_app_banner_manager_desktop.cc b/chrome/browser/banners/test_app_banner_manager_desktop.cc index 2d85d49..bb36845 100644 --- a/chrome/browser/banners/test_app_banner_manager_desktop.cc +++ b/chrome/browser/banners/test_app_banner_manager_desktop.cc
@@ -117,6 +117,17 @@ OnFinished(); } +void TestAppBannerManagerDesktop::DidFinishLoad( + content::RenderFrameHost* render_frame_host, + const GURL& validated_url) { + if (ShouldIgnore(render_frame_host, validated_url)) { + SetInstallable(false); + return; + } + + AppBannerManagerDesktop::DidFinishLoad(render_frame_host, validated_url); +} + void TestAppBannerManagerDesktop::UpdateState(AppBannerManager::State state) { AppBannerManager::UpdateState(state); @@ -128,7 +139,7 @@ } void TestAppBannerManagerDesktop::SetInstallable(bool installable) { - DCHECK(!installable_.has_value()); + DCHECK(!installable_.has_value() || installable_ == installable); installable_ = installable; if (installable_quit_closure_) std::move(installable_quit_closure_).Run();
diff --git a/chrome/browser/banners/test_app_banner_manager_desktop.h b/chrome/browser/banners/test_app_banner_manager_desktop.h index 154b43b..e4fe41a 100644 --- a/chrome/browser/banners/test_app_banner_manager_desktop.h +++ b/chrome/browser/banners/test_app_banner_manager_desktop.h
@@ -61,6 +61,8 @@ void OnInstall(blink::mojom::DisplayMode display) override; void DidFinishCreatingWebApp(const web_app::AppId& app_id, web_app::InstallResultCode code) override; + void DidFinishLoad(content::RenderFrameHost* render_frame_host, + const GURL& validated_url) override; void UpdateState(AppBannerManager::State state) override; private:
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index f21470e..a1c9739 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3773,7 +3773,7 @@ #endif // defined(OS_POSIX) && !defined(OS_MAC) #if defined(OS_WIN) -base::string16 ChromeContentBrowserClient::GetAppContainerSidForSandboxType( +std::wstring ChromeContentBrowserClient::GetAppContainerSidForSandboxType( sandbox::policy::SandboxType sandbox_type) { // TODO(wfh): Add support for more process types here. crbug.com/499523 switch (sandbox_type) { @@ -3803,7 +3803,7 @@ case sandbox::policy::SandboxType::kMediaFoundationCdm: // Should never reach here. CHECK(0); - return base::string16(); + return std::wstring(); } }
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h index 6dbae2cd..2328468 100644 --- a/chrome/browser/chrome_content_browser_client.h +++ b/chrome/browser/chrome_content_browser_client.h
@@ -406,7 +406,7 @@ bool PreSpawnChild(sandbox::TargetPolicy* policy, sandbox::policy::SandboxType sandbox_type, ChildSpawnFlags flags) override; - base::string16 GetAppContainerSidForSandboxType( + std::wstring GetAppContainerSidForSandboxType( sandbox::policy::SandboxType sandbox_type) override; bool IsRendererCodeIntegrityEnabled() override; #endif
diff --git a/chrome/browser/chromeos/arc/auth/arc_auth_service.cc b/chrome/browser/chromeos/arc/auth/arc_auth_service.cc index 06247c6..ec42e5c 100644 --- a/chrome/browser/chromeos/arc/auth/arc_auth_service.cc +++ b/chrome/browser/chromeos/arc/auth/arc_auth_service.cc
@@ -155,29 +155,29 @@ } void TriggerAccountManagerMigrationsIfRequired(Profile* profile) { - if (!chromeos::IsAccountManagerAvailable(profile)) + if (!ash::IsAccountManagerAvailable(profile)) return; - chromeos::AccountManagerMigrator* const migrator = - chromeos::AccountManagerMigratorFactory::GetForBrowserContext(profile); + ash::AccountManagerMigrator* const migrator = + ash::AccountManagerMigratorFactory::GetForBrowserContext(profile); if (!migrator) { // Migrator can be null for ephemeral and kiosk sessions. Ignore those cases // since there are no accounts to be migrated in that case. return; } - const base::Optional<chromeos::AccountMigrationRunner::MigrationResult> + const base::Optional<ash::AccountMigrationRunner::MigrationResult> last_migration_run_result = migrator->GetLastMigrationRunResult(); if (!last_migration_run_result) return; if (last_migration_run_result->final_status != - chromeos::AccountMigrationRunner::Status::kFailure) { + ash::AccountMigrationRunner::Status::kFailure) { return; } if (last_migration_run_result->failed_step_id != - chromeos::AccountManagerMigrator::kArcAccountsMigrationId) { + ash::AccountManagerMigrator::kArcAccountsMigrationId) { // Migrations failed but not because of ARC. ARC should not try to re-run // migrations in this case. return; @@ -504,25 +504,25 @@ void ArcAuthService::IsAccountManagerAvailable( IsAccountManagerAvailableCallback callback) { - std::move(callback).Run(chromeos::IsAccountManagerAvailable(profile_)); + std::move(callback).Run(ash::IsAccountManagerAvailable(profile_)); } void ArcAuthService::HandleAddAccountRequest() { - DCHECK(chromeos::IsAccountManagerAvailable(profile_)); + DCHECK(ash::IsAccountManagerAvailable(profile_)); chromeos::InlineLoginDialogChromeOS::ShowDeprecated( ::account_manager::AccountManagerFacade::AccountAdditionSource::kArc); } void ArcAuthService::HandleRemoveAccountRequest(const std::string& email) { - DCHECK(chromeos::IsAccountManagerAvailable(profile_)); + DCHECK(ash::IsAccountManagerAvailable(profile_)); chrome::SettingsWindowManager::GetInstance()->ShowOSSettings( profile_, chromeos::settings::mojom::kMyAccountsSubpagePath); } void ArcAuthService::HandleUpdateCredentialsRequest(const std::string& email) { - DCHECK(chromeos::IsAccountManagerAvailable(profile_)); + DCHECK(ash::IsAccountManagerAvailable(profile_)); chromeos::InlineLoginDialogChromeOS::ShowDeprecated( email, @@ -535,7 +535,7 @@ // proper Profile independent entity once we have that. DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - if (!chromeos::IsAccountManagerAvailable(profile_)) + if (!ash::IsAccountManagerAvailable(profile_)) return; // Ignore the update if ARC has not been provisioned yet. @@ -571,7 +571,7 @@ const AccountInfo& account_info) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - if (!chromeos::IsAccountManagerAvailable(profile_)) + if (!ash::IsAccountManagerAvailable(profile_)) return; DCHECK(!IsPrimaryGaiaAccount(account_info.gaia)); @@ -801,7 +801,7 @@ } void ArcAuthService::TriggerAccountsPushToArc(bool filter_primary_account) { - if (!chromeos::IsAccountManagerAvailable(profile_)) + if (!ash::IsAccountManagerAvailable(profile_)) return; const std::vector<CoreAccountInfo> accounts =
diff --git a/chrome/browser/chromeos/arc/fileapi/arc_file_system_bridge.cc b/chrome/browser/chromeos/arc/fileapi/arc_file_system_bridge.cc index cbaca25..3daa33ac 100644 --- a/chrome/browser/chromeos/arc/fileapi/arc_file_system_bridge.cc +++ b/chrome/browser/chromeos/arc/fileapi/arc_file_system_bridge.cc
@@ -514,7 +514,7 @@ case storage::FileSystemType::kFileSystemTypeDriveFs: case storage::FileSystemType::kFileSystemTypeSmbFs: return path; - case storage::FileSystemType::kFileSystemTypeNativeLocal: { + case storage::FileSystemType::kFileSystemTypeLocal: { base::FilePath crostini_mount_path = file_manager::util::GetCrostiniMountDirectory(profile); if (crostini_mount_path == path || crostini_mount_path.IsParent(path))
diff --git a/chrome/browser/chromeos/arc/fileapi/arc_file_system_bridge_unittest.cc b/chrome/browser/chromeos/arc/fileapi/arc_file_system_bridge_unittest.cc index c12f72a..2110f3c 100644 --- a/chrome/browser/chromeos/arc/fileapi/arc_file_system_bridge_unittest.cc +++ b/chrome/browser/chromeos/arc/fileapi/arc_file_system_bridge_unittest.cc
@@ -318,7 +318,7 @@ "path/to/file"); base::FilePath crostini_vfs_path = arc_file_system_bridge_->GetLinuxVFSPathForPathOnFileSystemType( - profile_, crostini_path, storage::kFileSystemTypeNativeLocal); + profile_, crostini_path, storage::kFileSystemTypeLocal); EXPECT_EQ(crostini_vfs_path, crostini_path); // Check: fuse-zip and rar2fs paths are returned as passed in. @@ -327,17 +327,16 @@ .Append("path/to/file"); base::FilePath archive_vfs_path = arc_file_system_bridge_->GetLinuxVFSPathForPathOnFileSystemType( - profile_, archive_path, storage::kFileSystemTypeNativeLocal); + profile_, archive_path, storage::kFileSystemTypeLocal); EXPECT_EQ(archive_vfs_path, archive_path); - // Check: Other kFileSystemTypeNativeLocal paths that are not descendants of + // Check: Other kFileSystemTypeLocal paths that are not descendants of // the Crostini, fuse-zip or rar2fs mount points return an empty path. const base::FilePath empty_path, unsupported_local_path = base::FilePath("/path/to/file"); base::FilePath unsupported_local_vfs_path = arc_file_system_bridge_->GetLinuxVFSPathForPathOnFileSystemType( - profile_, unsupported_local_path, - storage::kFileSystemTypeNativeLocal); + profile_, unsupported_local_path, storage::kFileSystemTypeLocal); EXPECT_EQ(empty_path, unsupported_local_vfs_path); // Check: Paths from unsupported FileSystemTypes return an empty path.
diff --git a/chrome/browser/chromeos/browser_context_keyed_service_factories.cc b/chrome/browser/chromeos/browser_context_keyed_service_factories.cc index 0ee5e65..c3921bb 100644 --- a/chrome/browser/chromeos/browser_context_keyed_service_factories.cc +++ b/chrome/browser/chromeos/browser_context_keyed_service_factories.cc
@@ -53,6 +53,8 @@ namespace chromeos { +using ::ash::AccountManagerMigratorFactory; + void EnsureBrowserContextKeyedServiceFactoriesBuilt() { AccountManagerMigratorFactory::GetInstance(); android_sms::AndroidSmsServiceFactory::GetInstance();
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc b/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc index e7b4eaeb..f47107f 100644 --- a/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc +++ b/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc
@@ -148,7 +148,7 @@ storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( file_manager::util::GetDownloadsMountPointName(profile()), - storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), profile()->GetPath()); tarball_ = file_manager::util::GetMyFilesFolderForProfile(profile()).Append( "crostini_export_import_unittest_tarball.tar.gz");
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc index 0c5aa47..ef03e559 100644 --- a/chrome/browser/chromeos/crostini/crostini_manager.cc +++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -759,7 +759,7 @@ base::FilePath mount_path = base::FilePath(mount_info.mount_path); storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( file_manager::util::GetCrostiniMountPointName(profile_), - storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), mount_path); // VolumeManager is null in unittest.
diff --git a/chrome/browser/chromeos/crostini/crostini_package_service_unittest.cc b/chrome/browser/chromeos/crostini/crostini_package_service_unittest.cc index 0b7a4d0..974dfa6 100644 --- a/chrome/browser/chromeos/crostini/crostini_package_service_unittest.cc +++ b/chrome/browser/chromeos/crostini/crostini_package_service_unittest.cc
@@ -191,7 +191,7 @@ std::string mount_point_name = file_manager::util::GetDownloadsMountPointName(profile_.get()); mount_points->RegisterFileSystem( - mount_point_name, storage::kFileSystemTypeNativeLocal, + mount_point_name, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), file_manager::util::GetDownloadsFolderForProfile(profile_.get())); package_file_url_ = mount_points->CreateExternalFileSystemURL(
diff --git a/chrome/browser/chromeos/exo/chrome_data_exchange_delegate_unittest.cc b/chrome/browser/chromeos/exo/chrome_data_exchange_delegate_unittest.cc index 1fe8760..73906b0 100644 --- a/chrome/browser/chromeos/exo/chrome_data_exchange_delegate_unittest.cc +++ b/chrome/browser/chromeos/exo/chrome_data_exchange_delegate_unittest.cc
@@ -83,14 +83,14 @@ myfiles_dir_ = file_manager::util::GetMyFilesFolderForProfile(profile_.get()); mount_points_->RegisterFileSystem( - myfiles_mount_name_, storage::kFileSystemTypeNativeLocal, + myfiles_mount_name_, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), myfiles_dir_); crostini_mount_name_ = file_manager::util::GetCrostiniMountPointName(profile_.get()); crostini_dir_ = file_manager::util::GetCrostiniMountDirectory(profile_.get()); mount_points_->RegisterFileSystem( - crostini_mount_name_, storage::kFileSystemTypeNativeLocal, + crostini_mount_name_, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), crostini_dir_); // DBus seneschal client.
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api_test.cc b/chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api_test.cc index cf4b972..7ff9744 100644 --- a/chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api_test.cc +++ b/chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api_test.cc
@@ -182,8 +182,7 @@ // Creates new, test mount point. void AddTmpMountPoint(const std::string& extension_id) { BrowserContext::GetMountPoints(browser()->profile()) - ->RegisterFileSystem("tmp", - storage::kFileSystemTypeNativeLocal, + ->RegisterFileSystem("tmp", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), tmp_mount_point_); }
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc index fb1f833..5e8b14e 100644 --- a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc +++ b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
@@ -161,7 +161,7 @@ ASSERT_TRUE( content::BrowserContext::GetMountPoints(profile)->RegisterFileSystem( - kLocalMountPointName, storage::kFileSystemTypeNativeLocal, + kLocalMountPointName, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), root)); file_manager::VolumeManager::Get(profile)->AddVolumeForTesting( root, file_manager::VOLUME_TYPE_TESTING, chromeos::DEVICE_TYPE_UNKNOWN,
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc index 96d3066..943b064 100644 --- a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc +++ b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
@@ -1352,7 +1352,7 @@ return RespondNow( Error("FileSystemBackend failed to handle the entry's url.")); } - if (file_system_url.type() != storage::kFileSystemTypeNativeLocal && + if (file_system_url.type() != storage::kFileSystemTypeLocal && file_system_url.type() != storage::kFileSystemTypeDriveFs) { return RespondNow(Error("Only local directories are supported.")); }
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc index bb22ac9..4f3b2f1a 100644 --- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc +++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -189,7 +189,7 @@ return true; case api::file_manager_private::SOURCE_RESTRICTION_NATIVE_SOURCE: - return type == storage::kFileSystemTypeNativeLocal; + return type == storage::kFileSystemTypeLocal; } }
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_thumbnail.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_thumbnail.cc index 5b447a29..3ff7b33 100644 --- a/chrome/browser/chromeos/extensions/file_manager/private_api_thumbnail.cc +++ b/chrome/browser/chromeos/extensions/file_manager/private_api_thumbnail.cc
@@ -206,7 +206,7 @@ const storage::FileSystemURL file_system_url = file_system_context->CrackURL(url); - if (file_system_url.type() != storage::kFileSystemTypeNativeLocal) { + if (file_system_url.type() != storage::kFileSystemTypeLocal) { return RespondNow(Error("Expected a native local URL")); }
diff --git a/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc b/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc index 2dbedec..b5eb735 100644 --- a/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc +++ b/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc
@@ -428,7 +428,7 @@ void AddTestMountPoint() override { EXPECT_TRUE( content::BrowserContext::GetMountPoints(profile())->RegisterFileSystem( - kLocalMountPointName, storage::kFileSystemTypeNativeLocal, + kLocalMountPointName, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), mount_point_dir_)); VolumeManager::Get(profile())->AddVolumeForTesting( mount_point_dir_, VOLUME_TYPE_TESTING, chromeos::DEVICE_TYPE_UNKNOWN, @@ -459,8 +459,7 @@ void AddTestMountPoint() override { EXPECT_TRUE( content::BrowserContext::GetMountPoints(profile())->RegisterFileSystem( - kRestrictedMountPointName, - storage::kFileSystemTypeRestrictedNativeLocal, + kRestrictedMountPointName, storage::kFileSystemTypeRestrictedLocal, storage::FileSystemMountOption(), mount_point_dir_)); VolumeManager::Get(profile())->AddVolumeForTesting( mount_point_dir_, VOLUME_TYPE_TESTING, chromeos::DEVICE_TYPE_UNKNOWN, @@ -667,7 +666,7 @@ void AddTestMountPoint() override { EXPECT_TRUE( content::BrowserContext::GetMountPoints(profile())->RegisterFileSystem( - kLocalMountPointName, storage::kFileSystemTypeNativeLocal, + kLocalMountPointName, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), local_mount_point_dir_)); VolumeManager::Get(profile())->AddVolumeForTesting( local_mount_point_dir_, VOLUME_TYPE_TESTING,
diff --git a/chrome/browser/chromeos/file_manager/file_browser_handlers.cc b/chrome/browser/chromeos/file_manager/file_browser_handlers.cc index 59136fc..3f72164 100644 --- a/chrome/browser/chromeos/file_manager/file_browser_handlers.cc +++ b/chrome/browser/chromeos/file_manager/file_browser_handlers.cc
@@ -228,8 +228,8 @@ base::FilePath virtual_path = url.virtual_path(); const bool is_native_file = - url.type() == storage::kFileSystemTypeNativeLocal || - url.type() == storage::kFileSystemTypeRestrictedNativeLocal; + url.type() == storage::kFileSystemTypeLocal || + url.type() == storage::kFileSystemTypeRestrictedLocal; // If the file is from a physical volume, actual file must be found. if (is_native_file) {
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc index ef99276b..3add445 100644 --- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc +++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
@@ -1015,8 +1015,8 @@ // Revoke name() mount point first, then re-add its mount point. GetMountPoints()->RevokeFileSystem(name()); const bool added = GetMountPoints()->RegisterFileSystem( - name(), storage::kFileSystemTypeNativeLocal, - storage::FileSystemMountOption(), root_path()); + name(), storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), + root_path()); if (!added) return false;
diff --git a/chrome/browser/chromeos/file_manager/file_tasks_notifier.cc b/chrome/browser/chromeos/file_manager/file_tasks_notifier.cc index 79b7007..bf740f14 100644 --- a/chrome/browser/chromeos/file_manager/file_tasks_notifier.cc +++ b/chrome/browser/chromeos/file_manager/file_tasks_notifier.cc
@@ -34,8 +34,8 @@ bool IsSupportedFileSystemType(storage::FileSystemType type) { switch (type) { - case storage::kFileSystemTypeNativeLocal: - case storage::kFileSystemTypeRestrictedNativeLocal: + case storage::kFileSystemTypeLocal: + case storage::kFileSystemTypeRestrictedLocal: case storage::kFileSystemTypeDriveFs: return true; default:
diff --git a/chrome/browser/chromeos/file_manager/file_tasks_notifier_unittest.cc b/chrome/browser/chromeos/file_manager/file_tasks_notifier_unittest.cc index 2666b43..1241975c 100644 --- a/chrome/browser/chromeos/file_manager/file_tasks_notifier_unittest.cc +++ b/chrome/browser/chromeos/file_manager/file_tasks_notifier_unittest.cc
@@ -37,7 +37,7 @@ storage::FileSystemURL CreateFileSystemUrl( const base::FilePath& path, - storage::FileSystemType type = storage::kFileSystemTypeNativeLocal) { + storage::FileSystemType type = storage::kFileSystemTypeLocal) { return storage::FileSystemURL::CreateForTest({}, {}, {}, "", type, path, "", {}); } @@ -140,7 +140,7 @@ ASSERT_TRUE(base::CreateDirectory(my_files_)); base::WriteFile(my_files_.Append("file"), "data", 4); ASSERT_TRUE(mount_points->RegisterFileSystem( - "downloads", storage::kFileSystemTypeNativeLocal, {}, my_files_)); + "downloads", storage::kFileSystemTypeLocal, {}, my_files_)); ASSERT_TRUE(mount_points->RegisterFileSystem( "drivefs", storage::kFileSystemTypeDriveFs, {}, base::FilePath("/media/fuse/drivefs")));
diff --git a/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc b/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc index 23282a9..694b022c 100644 --- a/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc +++ b/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc
@@ -1400,7 +1400,7 @@ void SetUp() override { storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( util::GetDownloadsMountPointName(&test_profile_), - storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), util::GetMyFilesFolderForProfile(&test_profile_)); }
diff --git a/chrome/browser/chromeos/file_manager/filesystem_api_util.cc b/chrome/browser/chromeos/file_manager/filesystem_api_util.cc index 0c8d23f5..fb28fa2 100644 --- a/chrome/browser/chromeos/file_manager/filesystem_api_util.cc +++ b/chrome/browser/chromeos/file_manager/filesystem_api_util.cc
@@ -127,8 +127,8 @@ bool IsNonNativeFileSystemType(storage::FileSystemType type) { switch (type) { - case storage::kFileSystemTypeNativeLocal: - case storage::kFileSystemTypeRestrictedNativeLocal: + case storage::kFileSystemTypeLocal: + case storage::kFileSystemTypeRestrictedLocal: case storage::kFileSystemTypeDriveFs: case storage::kFileSystemTypeSmbFs: return false;
diff --git a/chrome/browser/chromeos/file_manager/guest_os_file_tasks_unittest.cc b/chrome/browser/chromeos/file_manager/guest_os_file_tasks_unittest.cc index c2e56703..1d484b0 100644 --- a/chrome/browser/chromeos/file_manager/guest_os_file_tasks_unittest.cc +++ b/chrome/browser/chromeos/file_manager/guest_os_file_tasks_unittest.cc
@@ -40,7 +40,7 @@ void SetUp() override { storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( util::GetDownloadsMountPointName(&profile_), - storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), util::GetMyFilesFolderForProfile(&profile_)); fake_crostini_features_.set_enabled(true); fake_plugin_vm_features_.set_enabled(true);
diff --git a/chrome/browser/chromeos/file_manager/path_util_unittest.cc b/chrome/browser/chromeos/file_manager/path_util_unittest.cc index 96621dc0..6019b12 100644 --- a/chrome/browser/chromeos/file_manager/path_util_unittest.cc +++ b/chrome/browser/chromeos/file_manager/path_util_unittest.cc
@@ -98,9 +98,8 @@ // Mount the volume to test the return from mount_points. storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - GetDownloadsMountPointName(profile_.get()), - storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), - profile_path.Append("MyFiles")); + GetDownloadsMountPointName(profile_.get()), storage::kFileSystemTypeLocal, + storage::FileSystemMountOption(), profile_path.Append("MyFiles")); // When returning from the mount_point Downloads should still point to // MyFiles/Downloads. @@ -333,27 +332,27 @@ // Register crostini, my files, drive, android. mount_points->RegisterFileSystem(GetCrostiniMountPointName(profile_.get()), - storage::kFileSystemTypeNativeLocal, + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), GetCrostiniMountDirectory(profile_.get())); mount_points->RegisterFileSystem(GetDownloadsMountPointName(profile_.get()), - storage::kFileSystemTypeNativeLocal, + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), GetMyFilesFolderForProfile(profile_.get())); mount_points->RegisterFileSystem( - GetAndroidFilesMountPointName(), storage::kFileSystemTypeNativeLocal, + GetAndroidFilesMountPointName(), storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(kAndroidFilesPath)); drive::DriveIntegrationService* integration_service = drive::DriveIntegrationServiceFactory::GetForProfile(profile_.get()); base::FilePath mount_point_drive = integration_service->GetMountPointPath(); mount_points->RegisterFileSystem( - mount_point_drive.BaseName().value(), storage::kFileSystemTypeNativeLocal, + mount_point_drive.BaseName().value(), storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), mount_point_drive); mount_points->RegisterFileSystem( - chromeos::kSystemMountNameRemovable, storage::kFileSystemTypeNativeLocal, + chromeos::kSystemMountNameRemovable, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(kRemovableMediaPath)); mount_points->RegisterFileSystem( - chromeos::kSystemMountNameArchive, storage::kFileSystemTypeNativeLocal, + chromeos::kSystemMountNameArchive, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(kArchiveMountPath)); // SmbFsShare comes up with a unique stable ID for the share, it can // just be faked here, the mount ID is expected to appear in the mount @@ -488,15 +487,15 @@ std::string downloads_mount_name = GetDownloadsMountPointName(profile_.get()); base::FilePath downloads = GetDownloadsFolderForProfile(profile_.get()); mount_points->RegisterFileSystem(downloads_mount_name, - storage::kFileSystemTypeNativeLocal, + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), downloads); base::FilePath removable = base::FilePath(kRemovableMediaPath); mount_points->RegisterFileSystem( - chromeos::kSystemMountNameRemovable, storage::kFileSystemTypeNativeLocal, + chromeos::kSystemMountNameRemovable, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(kRemovableMediaPath)); base::FilePath archive = base::FilePath(kArchiveMountPath); mount_points->RegisterFileSystem( - chromeos::kSystemMountNameArchive, storage::kFileSystemTypeNativeLocal, + chromeos::kSystemMountNameArchive, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(kArchiveMountPath)); std::string relative_path_1 = "foo"; std::string relative_path_2 = "foo/bar"; @@ -623,7 +622,7 @@ // Add a crostini mount point for the primary profile. crostini_mount_point_ = GetCrostiniMountDirectory(primary_profile); mount_points->RegisterFileSystem(GetCrostiniMountPointName(primary_profile), - storage::kFileSystemTypeNativeLocal, + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), crostini_mount_point_);
diff --git a/chrome/browser/chromeos/file_manager/volume_manager.cc b/chrome/browser/chromeos/file_manager/volume_manager.cc index 94e2ff87..eee28d1 100644 --- a/chrome/browser/chromeos/file_manager/volume_manager.cc +++ b/chrome/browser/chromeos/file_manager/volume_manager.cc
@@ -82,10 +82,9 @@ // In some tests we want to override existing Downloads mount point, so we // first revoke the existing mount point (if any). mount_points->RevokeFileSystem(mount_point_name); - return mount_points->RegisterFileSystem(mount_point_name, - storage::kFileSystemTypeNativeLocal, - storage::FileSystemMountOption(), - path); + return mount_points->RegisterFileSystem( + mount_point_name, storage::kFileSystemTypeLocal, + storage::FileSystemMountOption(), path); } // Registers a mount point for Android files to ExternalMountPoints. @@ -94,7 +93,7 @@ storage::ExternalMountPoints::GetSystemInstance(); return mount_points->RegisterFileSystem( file_manager::util::GetAndroidFilesMountPointName(), - storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(util::kAndroidFilesPath)); } @@ -697,7 +696,7 @@ bool result = storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( file_manager::util::GetAndroidFilesMountPointName(), - storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), path); DCHECK(result); DoMountEvent(chromeos::MOUNT_ERROR_NONE, Volume::CreateForAndroidFiles(path)); @@ -753,7 +752,7 @@ bool success = storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( file_manager::util::GetCrostiniMountPointName(profile_), - storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), path); DoMountEvent( success ? chromeos::MOUNT_ERROR_NONE : chromeos::MOUNT_ERROR_INVALID_PATH,
diff --git a/chrome/browser/chromeos/fileapi/file_change_service_unittest.cc b/chrome/browser/chromeos/fileapi/file_change_service_unittest.cc index db4dd930..559b863 100644 --- a/chrome/browser/chromeos/fileapi/file_change_service_unittest.cc +++ b/chrome/browser/chromeos/fileapi/file_change_service_unittest.cc
@@ -113,7 +113,7 @@ ASSERT_TRUE( storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - name_, storage::kFileSystemTypeNativeLocal, + name_, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), temp_dir_.GetPath())); GetFileSystemContext(profile_) @@ -131,7 +131,7 @@ // Returns a file system URL for the specified path relative to `temp_dir_`. storage::FileSystemURL CreateFileSystemURL(const std::string& path) { return GetFileSystemContext(profile_)->CreateCrackedFileSystemURL( - origin_, storage::kFileSystemTypeNativeLocal, + origin_, storage::kFileSystemTypeLocal, temp_dir_.GetPath().Append(base::FilePath::FromUTF8Unsafe(path))); }
diff --git a/chrome/browser/chromeos/fileapi/file_system_backend.cc b/chrome/browser/chromeos/fileapi/file_system_backend.cc index bccee2b..3578def 100644 --- a/chrome/browser/chromeos/fileapi/file_system_backend.cc +++ b/chrome/browser/chromeos/fileapi/file_system_backend.cc
@@ -63,8 +63,8 @@ bool FileSystemBackend::CanHandleURL(const storage::FileSystemURL& url) { if (!url.is_valid()) return false; - return url.type() == storage::kFileSystemTypeNativeLocal || - url.type() == storage::kFileSystemTypeRestrictedNativeLocal || + return url.type() == storage::kFileSystemTypeLocal || + url.type() == storage::kFileSystemTypeRestrictedLocal || url.type() == storage::kFileSystemTypeProvided || url.type() == storage::kFileSystemTypeDeviceMediaAsFileStorage || url.type() == storage::kFileSystemTypeArcContent || @@ -104,15 +104,15 @@ // already exists, hence it's safe to call without checking if a mount // point already exists or not. system_mount_points_->RegisterFileSystem( - kSystemMountNameArchive, storage::kFileSystemTypeNativeLocal, + kSystemMountNameArchive, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), chromeos::CrosDisksClient::GetArchiveMountPoint()); system_mount_points_->RegisterFileSystem( - kSystemMountNameRemovable, storage::kFileSystemTypeNativeLocal, + kSystemMountNameRemovable, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(storage::FlushPolicy::FLUSH_ON_COMPLETION), chromeos::CrosDisksClient::GetRemovableDiskMountPoint()); system_mount_points_->RegisterFileSystem( - kSystemMountNameOem, storage::kFileSystemTypeRestrictedNativeLocal, + kSystemMountNameOem, storage::kFileSystemTypeRestrictedLocal, storage::FileSystemMountOption(), base::FilePath(FILE_PATH_LITERAL("/usr/share/oem"))); } @@ -120,9 +120,9 @@ bool FileSystemBackend::CanHandleType(storage::FileSystemType type) const { switch (type) { case storage::kFileSystemTypeExternal: - case storage::kFileSystemTypeRestrictedNativeLocal: - case storage::kFileSystemTypeNativeLocal: - case storage::kFileSystemTypeNativeForPlatformApp: + case storage::kFileSystemTypeRestrictedLocal: + case storage::kFileSystemTypeLocal: + case storage::kFileSystemTypeLocalForPlatformApp: case storage::kFileSystemTypeDeviceMediaAsFileStorage: case storage::kFileSystemTypeProvided: case storage::kFileSystemTypeArcContent: @@ -245,7 +245,7 @@ return true; const std::string& extension_id = url.origin().host(); - if (url.type() == storage::kFileSystemTypeRestrictedNativeLocal) { + if (url.type() == storage::kFileSystemTypeRestrictedLocal) { for (size_t i = 0; i < base::size(kOemAccessibleExtensions); ++i) { if (extension_id == kOemAccessibleExtensions[i]) return true; @@ -294,8 +294,8 @@ switch (type) { case storage::kFileSystemTypeProvided: return file_system_provider_delegate_->GetAsyncFileUtil(type); - case storage::kFileSystemTypeNativeLocal: - case storage::kFileSystemTypeRestrictedNativeLocal: + case storage::kFileSystemTypeLocal: + case storage::kFileSystemTypeRestrictedLocal: return local_file_util_.get(); case storage::kFileSystemTypeDeviceMediaAsFileStorage: return mtp_delegate_->GetAsyncFileUtil(type); @@ -356,8 +356,8 @@ std::make_unique<storage::FileSystemOperationContext>( context, MediaFileSystemBackend::MediaTaskRunner().get())); } - if (url.type() == storage::kFileSystemTypeNativeLocal || - url.type() == storage::kFileSystemTypeRestrictedNativeLocal || + if (url.type() == storage::kFileSystemTypeLocal || + url.type() == storage::kFileSystemTypeRestrictedLocal || url.type() == storage::kFileSystemTypeDriveFs || url.type() == storage::kFileSystemTypeSmbFs) { return new ObservableFileSystemOperationImpl( @@ -394,8 +394,8 @@ // TODO(fukino): Support in-place copy for DocumentsProvider. // crbug.com/953603. case storage::kFileSystemTypeArcDocumentsProvider: - case storage::kFileSystemTypeNativeLocal: - case storage::kFileSystemTypeRestrictedNativeLocal: + case storage::kFileSystemTypeLocal: + case storage::kFileSystemTypeRestrictedLocal: case storage::kFileSystemTypeArcContent: // TODO(crbug.com/939235): Implement in-place copy in SmbFs. case storage::kFileSystemTypeSmbFs: @@ -422,8 +422,8 @@ case storage::kFileSystemTypeProvided: return file_system_provider_delegate_->CreateFileStreamReader( url, offset, max_bytes_to_read, expected_modification_time, context); - case storage::kFileSystemTypeNativeLocal: - case storage::kFileSystemTypeRestrictedNativeLocal: + case storage::kFileSystemTypeLocal: + case storage::kFileSystemTypeRestrictedLocal: case storage::kFileSystemTypeDriveFs: case storage::kFileSystemTypeSmbFs: return std::unique_ptr<storage::FileStreamReader>( @@ -461,7 +461,7 @@ case storage::kFileSystemTypeProvided: return file_system_provider_delegate_->CreateFileStreamWriter( url, offset, context); - case storage::kFileSystemTypeNativeLocal: + case storage::kFileSystemTypeLocal: case storage::kFileSystemTypeDriveFs: case storage::kFileSystemTypeSmbFs: return storage::FileStreamWriter::CreateForLocalFile( @@ -475,7 +475,7 @@ return arc_documents_provider_delegate_->CreateFileStreamWriter( url, offset, context); // Read only file systems. - case storage::kFileSystemTypeRestrictedNativeLocal: + case storage::kFileSystemTypeRestrictedLocal: case storage::kFileSystemTypeArcContent: return std::unique_ptr<storage::FileStreamWriter>(); default: @@ -508,8 +508,8 @@ case storage::kFileSystemTypeDeviceMediaAsFileStorage: mtp_delegate_->GetRedirectURLForContents(url, std::move(callback)); return; - case storage::kFileSystemTypeNativeLocal: - case storage::kFileSystemTypeRestrictedNativeLocal: + case storage::kFileSystemTypeLocal: + case storage::kFileSystemTypeRestrictedLocal: case storage::kFileSystemTypeArcContent: case storage::kFileSystemTypeArcDocumentsProvider: case storage::kFileSystemTypeDriveFs:
diff --git a/chrome/browser/chromeos/fileapi/file_system_backend_unittest.cc b/chrome/browser/chromeos/fileapi/file_system_backend_unittest.cc index bd706020..8909376 100644 --- a/chrome/browser/chromeos/fileapi/file_system_backend_unittest.cc +++ b/chrome/browser/chromeos/fileapi/file_system_backend_unittest.cc
@@ -82,22 +82,18 @@ const size_t initial_root_dirs_size = backend.GetRootDirectories().size(); // Register 'local' test mount points. - mount_points->RegisterFileSystem("c", - storage::kFileSystemTypeNativeLocal, + mount_points->RegisterFileSystem("c", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(FPL("/a/b/c"))); - mount_points->RegisterFileSystem("d", - storage::kFileSystemTypeNativeLocal, + mount_points->RegisterFileSystem("d", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(FPL("/b/c/d"))); // Register system test mount points. - system_mount_points->RegisterFileSystem("d", - storage::kFileSystemTypeNativeLocal, + system_mount_points->RegisterFileSystem("d", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(FPL("/g/c/d"))); - system_mount_points->RegisterFileSystem("e", - storage::kFileSystemTypeNativeLocal, + system_mount_points->RegisterFileSystem("e", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(FPL("/g/d/e"))); @@ -129,20 +125,15 @@ // Initialize mount points. ASSERT_TRUE(system_mount_points->RegisterFileSystem( - "system", - storage::kFileSystemTypeNativeLocal, - storage::FileSystemMountOption(), + "system", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(FPL("/g/system")))); ASSERT_TRUE(mount_points->RegisterFileSystem( - "removable", - storage::kFileSystemTypeNativeLocal, + "removable", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), base::FilePath(FPL("/media/removable")))); ASSERT_TRUE(mount_points->RegisterFileSystem( - "oem", - storage::kFileSystemTypeRestrictedNativeLocal, - storage::FileSystemMountOption(), - base::FilePath(FPL("/usr/share/oem")))); + "oem", storage::kFileSystemTypeRestrictedLocal, + storage::FileSystemMountOption(), base::FilePath(FPL("/usr/share/oem")))); // Backend specific mount point access. EXPECT_FALSE(backend.IsAccessAllowed( @@ -169,11 +160,9 @@ // The extension cannot access new mount points. // TODO(tbarzic): This should probably be changed. - ASSERT_TRUE( - mount_points->RegisterFileSystem("test", - storage::kFileSystemTypeNativeLocal, - storage::FileSystemMountOption(), - base::FilePath(FPL("/foo/test")))); + ASSERT_TRUE(mount_points->RegisterFileSystem( + "test", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), + base::FilePath(FPL("/foo/test")))); EXPECT_FALSE(backend.IsAccessAllowed( CreateFileSystemURL(extension, "test_/foo", mount_points.get()))); @@ -197,7 +186,7 @@ nullptr, // smbfs_delegate mount_points.get(), system_mount_points.get()); - const storage::FileSystemType type = storage::kFileSystemTypeNativeLocal; + const storage::FileSystemType type = storage::kFileSystemTypeLocal; const storage::FileSystemMountOption option = storage::FileSystemMountOption();
diff --git a/chrome/browser/chromeos/fileapi/recent_model_unittest.cc b/chrome/browser/chromeos/fileapi/recent_model_unittest.cc index b1c3db3..c65dd0e98 100644 --- a/chrome/browser/chromeos/fileapi/recent_model_unittest.cc +++ b/chrome/browser/chromeos/fileapi/recent_model_unittest.cc
@@ -28,7 +28,7 @@ const base::Time& last_modified) { storage::FileSystemURL url = storage::FileSystemURL::CreateForTest( url::Origin(), // origin - storage::kFileSystemTypeNativeLocal, base::FilePath(name)); + storage::kFileSystemTypeLocal, base::FilePath(name)); return RecentFile(url, last_modified); }
diff --git a/chrome/browser/chromeos/full_restore/OWNERS b/chrome/browser/chromeos/full_restore/OWNERS new file mode 100644 index 0000000..b1c6d30c --- /dev/null +++ b/chrome/browser/chromeos/full_restore/OWNERS
@@ -0,0 +1,2 @@ +dominickn@chromium.org +nancylingwang@chromium.org
diff --git a/chrome/browser/chromeos/login/chrome_restart_request.cc b/chrome/browser/chromeos/login/chrome_restart_request.cc index 13c4d1c..1feed77 100644 --- a/chrome/browser/chromeos/login/chrome_restart_request.cc +++ b/chrome/browser/chromeos/login/chrome_restart_request.cc
@@ -30,6 +30,7 @@ #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/url_constants.h" +#include "chromeos/constants/chromeos_features.h" #include "chromeos/constants/chromeos_switches.h" #include "chromeos/cryptohome/cryptohome_parameters.h" #include "chromeos/dbus/constants/dbus_switches.h" @@ -210,6 +211,9 @@ chromeos::switches::kEnterpriseEnableForcedReEnrollment, chromeos::switches::kFormFactor, chromeos::switches::kHasChromeOSKeyboard, + chromeos::switches::kLacrosChromeAdditionalArgs, + chromeos::switches::kLacrosChromeAdditionalEnv, + chromeos::switches::kLacrosChromePath, chromeos::switches::kLoginProfile, chromeos::switches::kNaturalScrollDefault, chromeos::switches::kRlzPingDelay, @@ -236,6 +240,7 @@ void DeriveEnabledFeatures(base::CommandLine* out_command_line) { static const base::Feature* kForwardEnabledFeatures[] = { &ash::features::kAutoNightLight, + &chromeos::features::kLacrosSupport, }; std::vector<std::string> enabled_features;
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc index 32347de3..ea13b9870 100644 --- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc +++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc
@@ -520,6 +520,7 @@ RecordEasyUnlockScreenUnlockEvent(event); if (will_authenticate_using_easy_unlock()) { + // TODO(crbug.com/1171972): Deprecate the AuthMethodChoice metric. SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice( SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kSmartLock); SmartLockMetricsRecorder::RecordAuthResultUnlockSuccess(); @@ -528,6 +529,7 @@ } else { SmartLockMetricsRecorder::RecordAuthMethodChoiceUnlockPasswordState( GetSmartUnlockPasswordAuthEvent()); + // TODO(crbug.com/1171972): Deprecate the AuthMethodChoice metric. SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice( SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kOther); OnUserEnteredPassword();
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_signin_chromeos.cc b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_signin_chromeos.cc index ef0c5ae..81f766b5 100644 --- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_signin_chromeos.cc +++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_signin_chromeos.cc
@@ -362,12 +362,11 @@ proximity_auth::ScreenlockBridge::LockHandler::SIGNIN_SCREEN) return; - // Only record metrics for users who have enabled the feature. + // TODO(crbug.com/1171972): Deprecate this metric. Note also that checking + // IsEnabled() here often incorrectly returns false, because + // OnScreenDidUnlock() is occurring during user session startup. See + // https://crbug.com/1154766 for more. if (IsEnabled()) { - if (will_authenticate_using_easy_unlock()) { - SmartLockMetricsRecorder::RecordAuthResultSignInSuccess(); - } - SmartLockMetricsRecorder::RecordSmartLockSignInAuthMethodChoice( will_authenticate_using_easy_unlock() ? SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kSmartLock
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_user_login_flow.cc b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_user_login_flow.cc index a3cc7fc..88663f53 100644 --- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_user_login_flow.cc +++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_user_login_flow.cc
@@ -16,18 +16,20 @@ EasyUnlockUserLoginFlow::~EasyUnlockUserLoginFlow() {} bool EasyUnlockUserLoginFlow::HandleLoginFailure(const AuthFailure& failure) { - SmartLockMetricsRecorder::RecordAuthResultSignInFailure( - SmartLockMetricsRecorder::SmartLockAuthResultFailureReason:: - kUserControllerSignInFailure); - UMA_HISTOGRAM_ENUMERATION( - "SmartLock.AuthResult.SignIn.Failure.UserControllerAuth", - failure.reason(), AuthFailure::FailureReason::NUM_FAILURE_REASONS); Profile* profile = ProfileHelper::GetSigninProfile(); EasyUnlockService* service = EasyUnlockService::Get(profile); if (!service) return false; service->HandleAuthFailure(account_id()); service->RecordEasySignInOutcome(account_id(), false); + + SmartLockMetricsRecorder::RecordAuthResultSignInFailure( + SmartLockMetricsRecorder::SmartLockAuthResultFailureReason:: + kUserControllerSignInFailure); + UMA_HISTOGRAM_ENUMERATION( + "SmartLock.AuthResult.SignIn.Failure.UserControllerAuth", + failure.reason(), AuthFailure::FailureReason::NUM_FAILURE_REASONS); + UnregisterFlowSoon(); return true; } @@ -38,6 +40,7 @@ if (!service) return; service->RecordEasySignInOutcome(account_id(), true); + SmartLockMetricsRecorder::RecordAuthResultSignInSuccess(); } } // namespace chromeos
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc index c3b60e2..cc4f071a 100644 --- a/chrome/browser/chromeos/login/session/user_session_manager.cc +++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -159,12 +159,13 @@ #include "ui/base/ime/chromeos/input_method_util.h" #include "url/gurl.h" -using signin::ConsentLevel; - namespace chromeos { namespace { +using ::ash::AccountManagerMigratorFactory; +using ::signin::ConsentLevel; + // Time to wait for child policy refresh. If that time is exceeded session // should start with cached policy. constexpr base::TimeDelta kWaitForChildPolicyTimeout = @@ -1142,7 +1143,7 @@ ProfileHelper::GetProfilePathByUserIdHash(user_context_.GetUserIDHash()); if (ProfileHelper::IsRegularProfilePath(profile_path)) { - chromeos::InitializeAccountManager( + ash::InitializeAccountManager( profile_path, base::BindOnce(&UserSessionManager::PrepareProfile, AsWeakPtr(), profile_path) /* initialization_callback */); @@ -1321,7 +1322,19 @@ identity_manager->GetPrimaryAccountMutator() ->SetUnconsentedPrimaryAccount(account_info->account_id); } - CHECK(identity_manager->HasPrimaryAccount(ConsentLevel::kNotRequired)); + + // TODO(https://crbug.com/1166265): Replace logs once issue is resolved. + if (!identity_manager->HasPrimaryAccount(ConsentLevel::kNotRequired)) { + if (account_info.has_value()) { + LOG(FATAL) << "IdentityManager missing primary account. " + << "GAIA ID: " << gaia_id << ", " + << "Account GAIA: " << account_info->gaia << ", " + << "Account email: " << account_info->email; + } else { + LOG(FATAL) << "IdentityManager missing primary account. " + << "GAIA ID: " << gaia_id << ", Account info missing"; + } + } CHECK_EQ( identity_manager->GetPrimaryAccountInfo(ConsentLevel::kNotRequired) .gaia, @@ -1332,8 +1345,25 @@ // profile might only have an unconsented primary account. identity_manager->GetPrimaryAccountMutator()->SetPrimaryAccount( account_info->account_id); - CHECK(identity_manager->HasPrimaryAccount(ConsentLevel::kSync)); - CHECK_EQ(identity_manager->GetPrimaryAccountInfo().gaia, gaia_id); + + // TODO(https://crbug.com/1166265): Replace logs once issue is resolved. + if (!identity_manager->HasPrimaryAccount(ConsentLevel::kSync)) { + if (account_info.has_value()) { + LOG(FATAL) << "IdentityManager missing primary account. " + << "GAIA ID: " << gaia_id << ", " + << "Account GAIA: " << account_info->gaia << ", " + << "Account email: " << account_info->email; + } else { + LOG(FATAL) << "IdentityManager missing primary account. " + << "GAIA ID: " << gaia_id << ", Account info missing"; + } + } + if (identity_manager->GetPrimaryAccountInfo().gaia != gaia_id) { + LOG(FATAL) << "IdentityManager GAIA ID is mismatched. " + << "Primary account GAIA ID: " + << identity_manager->GetPrimaryAccountInfo().gaia + << ", Expected: " << gaia_id; + } } CoreAccountId account_id = @@ -2022,8 +2052,7 @@ void UserSessionManager::StartAccountManagerMigration(Profile* profile) { // `migrator` is nullptr for incognito profiles. - auto* migrator = - chromeos::AccountManagerMigratorFactory::GetForBrowserContext(profile); + auto* migrator = AccountManagerMigratorFactory::GetForBrowserContext(profile); if (migrator) migrator->Start(); }
diff --git a/chrome/browser/chromeos/login/signin/signin_error_notifier_ash.cc b/chrome/browser/chromeos/login/signin/signin_error_notifier_ash.cc index 58f2435..0f7ef6a0 100644 --- a/chrome/browser/chromeos/login/signin/signin_error_notifier_ash.cc +++ b/chrome/browser/chromeos/login/signin/signin_error_notifier_ash.cc
@@ -168,7 +168,7 @@ const AccountId account_id = multi_user_util::GetAccountIdFromProfile(profile_); - if (!chromeos::IsAccountManagerAvailable(profile_)) { + if (!ash::IsAccountManagerAvailable(profile_)) { // If this flag is disabled, Chrome OS does not have a concept of Secondary // Accounts. Preserve existing behavior. RecordReauthReason(account_id, chromeos::ReauthReason::SYNC_FAILED);
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_files_unittest.cc b/chrome/browser/chromeos/plugin_vm/plugin_vm_files_unittest.cc index 887f353..db4bae6 100644 --- a/chrome/browser/chromeos/plugin_vm/plugin_vm_files_unittest.cc +++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_files_unittest.cc
@@ -68,7 +68,7 @@ mount_points_ = storage::ExternalMountPoints::GetSystemInstance(); mount_name_ = file_manager::util::GetDownloadsMountPointName(&profile_); mount_points_->RegisterFileSystem( - mount_name_, storage::kFileSystemTypeNativeLocal, + mount_name_, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), GetMyFilesFolderPath()); } @@ -212,7 +212,7 @@ // Path in different volume. mount_points_->RegisterFileSystem( - "other-volume", storage::kFileSystemTypeNativeLocal, + "other-volume", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), GetMyFilesFolderPath()); storage::FileSystemURL url = mount_points_->CreateExternalFileSystemURL( url::Origin(), "other-volume", base::FilePath("other/volume"));
diff --git a/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc index 8d1faf7..23dab9b5 100644 --- a/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc +++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
@@ -812,8 +812,8 @@ // Setup a fake file system that should show up in mount points. storage::ExternalMountPoints::GetSystemInstance()->RevokeAllFileSystems(); storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - "c", storage::kFileSystemTypeNativeLocal, - storage::FileSystemMountOption(), base::FilePath(kExternalMountPoint)); + "c", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), + base::FilePath(kExternalMountPoint)); // Just verify that we are properly setting the mount points. std::vector<storage::MountPoints::MountPointInfo> external_mount_points;
diff --git a/chrome/browser/chromeos/ui/screen_capture_notification_ui_chromeos.cc b/chrome/browser/chromeos/ui/screen_capture_notification_ui_chromeos.cc index 1da4472..fb22c19e 100644 --- a/chrome/browser/chromeos/ui/screen_capture_notification_ui_chromeos.cc +++ b/chrome/browser/chromeos/ui/screen_capture_notification_ui_chromeos.cc
@@ -37,11 +37,6 @@ return 0; } -void ScreenCaptureNotificationUIChromeOS::SetStopCallback( - base::OnceClosure stop_callback) { - stop_callback_ = std::move(stop_callback); -} - void ScreenCaptureNotificationUIChromeOS::ProcessStopRequestFromUI() { if (!stop_callback_.is_null()) { std::move(stop_callback_).Run();
diff --git a/chrome/browser/chromeos/ui/screen_capture_notification_ui_chromeos.h b/chrome/browser/chromeos/ui/screen_capture_notification_ui_chromeos.h index c159133..7eb10ab6 100644 --- a/chrome/browser/chromeos/ui/screen_capture_notification_ui_chromeos.h +++ b/chrome/browser/chromeos/ui/screen_capture_notification_ui_chromeos.h
@@ -23,8 +23,6 @@ base::OnceClosure stop_callback, content::MediaStreamUI::SourceCallback source_callback) override; - void SetStopCallback(base::OnceClosure stop_callback) override; - private: void ProcessStopRequestFromUI();
diff --git a/chrome/browser/chromeos/usb/cros_usb_detector.cc b/chrome/browser/chromeos/usb/cros_usb_detector.cc index c1839336..dba9576 100644 --- a/chrome/browser/chromeos/usb/cros_usb_detector.cc +++ b/chrome/browser/chromeos/usb/cros_usb_detector.cc
@@ -557,6 +557,15 @@ new_device.sharable_with_crostini = has_supported_interface || new_device.allowed_interfaces_mask != 0; + // Storage devices already plugged in at log-in time will already be mounted. + for (const auto& iter : disks::DiskMountManager::GetInstance()->disks()) { + if (iter.second->bus_number() == device_info->bus_number && + iter.second->device_number() == device_info->port_number && + iter.second->is_mounted()) { + new_device.mount_points.insert(iter.second->mount_path()); + } + } + usb_devices_.push_back(new_device); available_device_info_.emplace(device_info->guid, device_info.Clone()); SignalUsbDeviceObservers();
diff --git a/chrome/browser/chromeos/usb/cros_usb_detector_unittest.cc b/chrome/browser/chromeos/usb/cros_usb_detector_unittest.cc index f977506..b71f675a 100644 --- a/chrome/browser/chromeos/usb/cros_usb_detector_unittest.cc +++ b/chrome/browser/chromeos/usb/cros_usb_detector_unittest.cc
@@ -250,6 +250,8 @@ const std::string& name, chromeos::disks::DiskMountManager::MountEvent event, chromeos::MountError mount_error = chromeos::MOUNT_ERROR_NONE) { + // In theory we should also clear the mounted flag from the disk, but we + // don't rely on that. chromeos::disks::DiskMountManager::MountPointInfo info( "/dev/" + name, "/mount/" + name, chromeos::MOUNT_TYPE_DEVICE, chromeos::disks::MOUNT_CONDITION_NONE); @@ -1090,14 +1092,23 @@ ConnectToDeviceManager(); base::RunLoop().RunUntilIdle(); + // Disks mounted before the usb device is detected by the CrosUsbDetector + // require a prompt. + AddDisk("disk_early", 1, 5, true); + device_manager_.CreateAndAddDevice( 0x0200, 0xff, 0xff, 0xff, 0x0100, 1, 2, /*bus_number=*/1, /*port_number=*/5, kManufacturerName, kProductName_1, "5"); base::RunLoop().RunUntilIdle(); auto device_info = GetSingleDeviceInfo(); + EXPECT_TRUE(cros_usb_detector_->SharingRequiresReassignPrompt(device_info)); + + NotifyMountEvent("disk_early", chromeos::disks::DiskMountManager::UNMOUNTING); + device_info = GetSingleDeviceInfo(); EXPECT_FALSE(cros_usb_detector_->SharingRequiresReassignPrompt(device_info)); + // A disk which fails to mount shouldn't cause the prompt to be shown. AddDisk("disk_error", 1, 5, /*mounted=*/false); NotifyMountEvent("disk_error", chromeos::disks::DiskMountManager::MOUNTING, chromeos::MOUNT_ERROR_INTERNAL); @@ -1107,9 +1118,4 @@ AddDisk("disk_success", 1, 5, true); device_info = GetSingleDeviceInfo(); EXPECT_TRUE(cros_usb_detector_->SharingRequiresReassignPrompt(device_info)); - - NotifyMountEvent("disk_success", - chromeos::disks::DiskMountManager::UNMOUNTING); - device_info = GetSingleDeviceInfo(); - EXPECT_FALSE(cros_usb_detector_->SharingRequiresReassignPrompt(device_info)); }
diff --git a/chrome/browser/devtools/devtools_file_helper.cc b/chrome/browser/devtools/devtools_file_helper.cc index 2a7c15ae..bde2af7 100644 --- a/chrome/browser/devtools/devtools_file_helper.cc +++ b/chrome/browser/devtools/devtools_file_helper.cc
@@ -155,7 +155,7 @@ std::string root_name(kRootName); storage::IsolatedContext::ScopedFSHandle file_system = isolated_context()->RegisterFileSystemForPath( - storage::kFileSystemTypeNativeLocal, std::string(), path, &root_name); + storage::kFileSystemTypeLocal, std::string(), path, &root_name); content::ChildProcessSecurityPolicy* policy = content::ChildProcessSecurityPolicy::GetInstance();
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.cc b/chrome/browser/extensions/api/developer_private/developer_private_api.cc index ed13891..e0618a9a 100644 --- a/chrome/browser/extensions/api/developer_private/developer_private_api.cc +++ b/chrome/browser/extensions/api/developer_private/developer_private_api.cc
@@ -1433,8 +1433,8 @@ storage::kFileSystemTypeIsolated, virtual_path); if (directory_url.is_valid() && - directory_url.type() != storage::kFileSystemTypeNativeLocal && - directory_url.type() != storage::kFileSystemTypeRestrictedNativeLocal && + directory_url.type() != storage::kFileSystemTypeLocal && + directory_url.type() != storage::kFileSystemTypeRestrictedLocal && directory_url.type() != storage::kFileSystemTypeDragged) { return LoadByFileSystemAPI(directory_url); }
diff --git a/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc b/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc index 303e330..8acf3137 100644 --- a/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc +++ b/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc
@@ -171,7 +171,7 @@ std::string register_name = "fs"; const storage::IsolatedContext::ScopedFSHandle file_system = isolated_context->RegisterFileSystemForPath( - storage::kFileSystemTypeNativeForPlatformApp, + storage::kFileSystemTypeLocalForPlatformApp, std::string() /* file_system_id */, original_url.path(), ®ister_name); if (!file_system.is_valid()) {
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc index 0273ed21..0f653bfb 100644 --- a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc +++ b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
@@ -275,7 +275,7 @@ base::CreateDirectory(mount_point_path.Append(kChildDirectory))); ASSERT_TRUE(content::BrowserContext::GetMountPoints(browser()->profile()) ->RegisterFileSystem( - mount_point_name, storage::kFileSystemTypeNativeLocal, + mount_point_name, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), mount_point_path)); VolumeManager* const volume_manager = VolumeManager::Get(browser()->profile());
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc index 42952b84..e08c8ae 100644 --- a/chrome/browser/extensions/api/settings_private/prefs_util.cc +++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -475,6 +475,8 @@ (*s_allowlist) [ash::prefs::kAccessibilityScreenMagnifierFocusFollowingEnabled] = settings_api::PrefType::PREF_TYPE_BOOLEAN; + (*s_allowlist)[ash::prefs::kAccessibilityScreenMagnifierMousePanningMode] = + settings_api::PrefType::PREF_TYPE_NUMBER; (*s_allowlist)[ash::prefs::kAccessibilityScreenMagnifierScale] = settings_api::PrefType::PREF_TYPE_NUMBER; (*s_allowlist)[ash::prefs::kAccessibilitySelectToSpeakEnabled] =
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index 750fae6..696b0c3 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -2418,6 +2418,11 @@ "expiry_milestone": 90 }, { + "name": "enable-vaapi-av1-decode-acceleration", + "owners": [ "andrescj", "chromeos-gfx-video@google.com" ], + "expiry_milestone": 93 + }, + { "name": "enable-vaapi-jpeg-image-decode-acceleration", "owners": [ "andrescj", "chromeos-gfx@google.com" ], "expiry_milestone": 90 @@ -2619,6 +2624,11 @@ "expiry_milestone": 90 }, { + "name": "exo-lock-notification", + "owners": [ "cpelling@google.com" ], + "expiry_milestone": 95 + }, + { "name": "exo-ordinal-motion", "owners": [ "hollingum@google.com" ], "expiry_milestone": 90 @@ -3421,11 +3431,6 @@ "expiry_milestone": 92 }, { - "name": "mojo-linux-sharedmem", - "owners": ["bgeffon", "rockot"], - "expiry_milestone": 99 - }, - { "name": "mouse-subframe-no-implicit-capture", "owners": [ "eirage", "nzolghadr", "input-dev" ], "expiry_milestone": 84
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 880be9bb..8f64505 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -1442,12 +1442,6 @@ const char kMobilePwaInstallUseBottomSheetDescription[] = "Enables use of a rich bottom sheet when offering mobile PWA installation."; -const char kMojoLinuxChannelSharedMemName[] = - "Enable Mojo Shared Memory Channel"; -const char kMojoLinuxChannelSharedMemDescription[] = - "If enabled Mojo on Linux based platforms can use shared memory as an " - "alternate channel for most messages."; - const char kMouseSubframeNoImplicitCaptureName[] = "Disable mouse implicit capture for iframe"; const char kMouseSubframeNoImplicitCaptureDescription[] = @@ -4282,6 +4276,11 @@ "Allow Linux applications to request a pointer lock, i.e. exclusive use of " "the mouse pointer."; +const char kExoLockNotificationName[] = "Notification bubble for UI lock"; +const char kExoLockNotificationDescription[] = + "Show a notification bubble once an application has switched to " + "non-immersive fullscreen mode or obtained pointer lock."; + const char kExperimentalAccessibilityChromeVoxAnnotationsName[] = "Enable experimental ChromeVox annotations feature."; const char kExperimentalAccessibilityChromeVoxAnnotationsDescription[] = @@ -4727,6 +4726,10 @@ "Enable unified media view to browse recently-modified media files from" " local disk, Google Drive, and Android."; +const char kVaapiAV1DecoderName[] = "VA-API decode acceleration for AV1"; +const char kVaapiAV1DecoderDescription[] = + "Enable or disable decode acceleration of AV1 videos using the VA-API."; + const char kVaapiJpegImageDecodeAccelerationName[] = "VA-API JPEG decode acceleration for images"; const char kVaapiJpegImageDecodeAccelerationDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index c45847fe..5ee5276 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -853,9 +853,6 @@ extern const char kMobilePwaInstallUseBottomSheetName[]; extern const char kMobilePwaInstallUseBottomSheetDescription[]; -extern const char kMojoLinuxChannelSharedMemName[]; -extern const char kMojoLinuxChannelSharedMemDescription[]; - extern const char kMouseSubframeNoImplicitCaptureName[]; extern const char kMouseSubframeNoImplicitCaptureDescription[]; @@ -2499,6 +2496,9 @@ extern const char kExoPointerLockName[]; extern const char kExoPointerLockDescription[]; +extern const char kExoLockNotificationName[]; +extern const char kExoLockNotificationDescription[]; + extern const char kExperimentalAccessibilityChromeVoxAnnotationsName[]; extern const char kExperimentalAccessibilityChromeVoxAnnotationsDescription[]; @@ -2760,6 +2760,10 @@ extern const char kUseFakeDeviceForMediaStreamName[]; extern const char kUseFakeDeviceForMediaStreamDescription[]; +// TODO(b/177462291): make flag available on LaCrOS. +extern const char kVaapiAV1DecoderName[]; +extern const char kVaapiAV1DecoderDescription[]; + extern const char kVaapiJpegImageDecodeAccelerationName[]; extern const char kVaapiJpegImageDecodeAccelerationDescription[];
diff --git a/chrome/browser/media/router/mojo/media_router_desktop.cc b/chrome/browser/media/router/mojo/media_router_desktop.cc index 3f831b0c..acce2f6 100644 --- a/chrome/browser/media/router/mojo/media_router_desktop.cc +++ b/chrome/browser/media/router/mojo/media_router_desktop.cc
@@ -132,8 +132,6 @@ config->use_mirroring_service = true; std::move(callback).Run(instance_id(), std::move(config)); - SyncStateToMediaRouteProvider(provider_id); - if (provider_id == MediaRouteProviderId::EXTENSION) { RegisterExtensionMediaRouteProvider(std::move(media_route_provider_remote)); } else { @@ -144,6 +142,8 @@ weak_factory_.GetWeakPtr(), provider_id)); media_route_providers_[provider_id] = std::move(bound_remote); } + + SyncStateToMediaRouteProvider(provider_id); } void MediaRouterDesktop::OnSinksReceived(
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc index 65e82da..4166679a 100644 --- a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc +++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
@@ -1089,6 +1089,7 @@ } break; case MediaRouteProviderId::ANDROID_CAF: + case MediaRouteProviderId::TEST: case MediaRouteProviderId::UNKNOWN: break; }
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.h b/chrome/browser/media/router/mojo/media_router_mojo_impl.h index 49679d6f..02af2dc 100644 --- a/chrome/browser/media/router/mojo/media_router_mojo_impl.h +++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.h
@@ -156,6 +156,7 @@ friend class MediaRouterFactory; friend class MediaRouterMojoImplTest; friend class MediaRouterMojoTest; + friend class MediaRouterNativeIntegrationBrowserTest; FRIEND_TEST_ALL_PREFIXES(MediaRouterMojoImplTest, JoinRouteTimedOutFails); FRIEND_TEST_ALL_PREFIXES(MediaRouterMojoImplTest, JoinRouteIncognitoMismatchFails);
diff --git a/chrome/browser/media/router/providers/test/test_media_route_provider.cc b/chrome/browser/media/router/providers/test/test_media_route_provider.cc index 95bc9cf..4581fd37 100644 --- a/chrome/browser/media/router/providers/test/test_media_route_provider.cc +++ b/chrome/browser/media/router/providers/test/test_media_route_provider.cc
@@ -37,7 +37,7 @@ } // namespace const MediaRouteProviderId TestMediaRouteProvider::kProviderId = - MediaRouteProviderId::CAST; + MediaRouteProviderId::TEST; TestMediaRouteProvider::TestMediaRouteProvider( mojo::PendingReceiver<mojom::MediaRouteProvider> receiver, @@ -49,11 +49,17 @@ kProviderId, mojom::MediaRouter::SinkAvailability::PER_SOURCE); } +TestMediaRouteProvider::~TestMediaRouteProvider() = default; + void TestMediaRouteProvider::SetSinks() { MediaSinkInternal sink_internal_1; MediaSinkInternal sink_internal_2; - sink_internal_1.set_sink(MediaSink("id1", "test-sink-1", SinkIconType::CAST)); - sink_internal_2.set_sink(MediaSink("id2", "test-sink-2", SinkIconType::CAST)); + MediaSink sink1("id1", "test-sink-1", SinkIconType::CAST); + MediaSink sink2("id2", "test-sink-2", SinkIconType::CAST); + sink1.set_provider_id(kProviderId); + sink2.set_provider_id(kProviderId); + sink_internal_1.set_sink(sink1); + sink_internal_2.set_sink(sink2); sinks_ = {sink_internal_1, sink_internal_2}; } @@ -207,29 +213,19 @@ } void TestMediaRouteProvider::StopObservingMediaSinks( - const std::string& media_source) { - NOTIMPLEMENTED(); -} + const std::string& media_source) {} void TestMediaRouteProvider::StartObservingMediaRoutes( - const std::string& media_source) { - NOTIMPLEMENTED(); -} + const std::string& media_source) {} void TestMediaRouteProvider::StopObservingMediaRoutes( - const std::string& media_source) { - NOTIMPLEMENTED(); -} + const std::string& media_source) {} void TestMediaRouteProvider::StartListeningForRouteMessages( - const std::string& route_id) { - NOTIMPLEMENTED(); -} + const std::string& route_id) {} void TestMediaRouteProvider::StopListeningForRouteMessages( - const std::string& route_id) { - NOTIMPLEMENTED(); -} + const std::string& route_id) {} void TestMediaRouteProvider::DetachRoute(const std::string& route_id) { media_router_->OnPresentationConnectionClosed( @@ -237,12 +233,9 @@ "Close route"); } -void TestMediaRouteProvider::EnableMdnsDiscovery() { - NOTIMPLEMENTED(); -} +void TestMediaRouteProvider::EnableMdnsDiscovery() {} void TestMediaRouteProvider::UpdateMediaSinks(const std::string& media_source) { - NOTIMPLEMENTED(); } void TestMediaRouteProvider::CreateMediaRouteController( @@ -250,13 +243,15 @@ mojo::PendingReceiver<mojom::MediaController> media_controller, mojo::PendingRemote<mojom::MediaStatusObserver> observer, CreateMediaRouteControllerCallback callback) { - NOTIMPLEMENTED(); + std::move(callback).Run(false); } void TestMediaRouteProvider::ProvideSinks( const std::string& provider_name, - const std::vector<media_router::MediaSinkInternal>& sinks) { - NOTIMPLEMENTED(); + const std::vector<media_router::MediaSinkInternal>& sinks) {} + +void TestMediaRouteProvider::GetState(GetStateCallback callback) { + std::move(callback).Run(nullptr); } std::vector<MediaRoute> TestMediaRouteProvider::GetMediaRoutes() {
diff --git a/chrome/browser/media/router/providers/test/test_media_route_provider.h b/chrome/browser/media/router/providers/test/test_media_route_provider.h index 6a3f274..a0aa03d 100644 --- a/chrome/browser/media/router/providers/test/test_media_route_provider.h +++ b/chrome/browser/media/router/providers/test/test_media_route_provider.h
@@ -75,6 +75,7 @@ void ProvideSinks( const std::string& provider_name, const std::vector<media_router::MediaSinkInternal>& sinks) override; + void GetState(GetStateCallback callback) override; private: void set_close_route_error_on_send() {
diff --git a/chrome/browser/media/webrtc/media_stream_capture_indicator.cc b/chrome/browser/media/webrtc/media_stream_capture_indicator.cc index 1a8cf3d..aa23936 100644 --- a/chrome/browser/media/webrtc/media_stream_capture_indicator.cc +++ b/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
@@ -248,12 +248,6 @@ #endif } - void SetStopCallback(base::OnceClosure stop_callback) override { - if (ui_) { - ui_->SetStopCallback(std::move(stop_callback)); - } - } - base::WeakPtr<WebContentsDeviceUsage> device_usage_; const blink::MediaStreamDevices devices_; const std::unique_ptr<::MediaStreamUI> ui_;
diff --git a/chrome/browser/media/webrtc/media_stream_capture_indicator.h b/chrome/browser/media/webrtc/media_stream_capture_indicator.h index c200c34c..18dc1a8 100644 --- a/chrome/browser/media/webrtc/media_stream_capture_indicator.h +++ b/chrome/browser/media/webrtc/media_stream_capture_indicator.h
@@ -41,9 +41,6 @@ virtual gfx::NativeViewId OnStarted( base::OnceClosure stop_callback, content::MediaStreamUI::SourceCallback source_callback) = 0; - - // Replaces the stop callback set in OnStarted(), if any. - virtual void SetStopCallback(base::OnceClosure stop) = 0; }; // Keeps track of which WebContents are capturing media streams. Used to display
diff --git a/chrome/browser/media/webrtc/webrtc_logging_controller.cc b/chrome/browser/media/webrtc/webrtc_logging_controller.cc index 1a5a4d7..69680324 100644 --- a/chrome/browser/media/webrtc/webrtc_logging_controller.cc +++ b/chrome/browser/media/webrtc/webrtc_logging_controller.cc
@@ -303,9 +303,9 @@ std::string registered_name; storage::IsolatedContext::ScopedFSHandle file_system = - isolated_context->RegisterFileSystemForPath( - storage::kFileSystemTypeNativeLocal, std::string(), logs_path, - ®istered_name); + isolated_context->RegisterFileSystemForPath(storage::kFileSystemTypeLocal, + std::string(), logs_path, + ®istered_name); // Only granting read and delete access to reduce contention with // webrtcLogging APIs that modify files in that folder.
diff --git a/chrome/browser/media_galleries/fileapi/media_file_system_backend.cc b/chrome/browser/media_galleries/fileapi/media_file_system_backend.cc index 4677870..2bcd68e 100644 --- a/chrome/browser/media_galleries/fileapi/media_file_system_backend.cc +++ b/chrome/browser/media_galleries/fileapi/media_file_system_backend.cc
@@ -216,7 +216,7 @@ bool MediaFileSystemBackend::CanHandleType(storage::FileSystemType type) const { switch (type) { - case storage::kFileSystemTypeNativeMedia: + case storage::kFileSystemTypeLocalMedia: case storage::kFileSystemTypeDeviceMedia: return true; default: @@ -241,7 +241,7 @@ // We count file system usages here, because we want to count (per session) // when the file system is actually used for I/O, rather than merely present. switch (type) { - case storage::kFileSystemTypeNativeMedia: + case storage::kFileSystemTypeLocalMedia: return native_media_file_util_.get(); #if defined(OS_WIN) || defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH) case storage::kFileSystemTypeDeviceMedia: @@ -265,7 +265,7 @@ DCHECK(error_code); *error_code = base::File::FILE_OK; switch (type) { - case storage::kFileSystemTypeNativeMedia: + case storage::kFileSystemTypeLocalMedia: case storage::kFileSystemTypeDeviceMedia: if (!media_copy_or_move_file_validator_factory_) { *error_code = base::File::FILE_ERROR_SECURITY; @@ -301,7 +301,7 @@ bool MediaFileSystemBackend::HasInplaceCopyImplementation( storage::FileSystemType type) const { - DCHECK(type == storage::kFileSystemTypeNativeMedia || + DCHECK(type == storage::kFileSystemTypeLocalMedia || type == storage::kFileSystemTypeDeviceMedia); return true; }
diff --git a/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc b/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc index 082f309..e2b9b0e1 100644 --- a/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc +++ b/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc
@@ -148,7 +148,7 @@ ASSERT_TRUE(base::CreateDirectory(dest_path)); dest_fs_ = storage::IsolatedContext::GetInstance()->RegisterFileSystemForPath( - storage::kFileSystemTypeNativeMedia, std::string(), dest_path, + storage::kFileSystemTypeLocalMedia, std::string(), dest_path, nullptr); size_t extension_index = filename.find_last_of(".");
diff --git a/chrome/browser/media_galleries/fileapi/native_media_file_util_unittest.cc b/chrome/browser/media_galleries/fileapi/native_media_file_util_unittest.cc index ec3b3a91..8d7bc6ec 100644 --- a/chrome/browser/media_galleries/fileapi/native_media_file_util_unittest.cc +++ b/chrome/browser/media_galleries/fileapi/native_media_file_util_unittest.cc
@@ -142,7 +142,7 @@ storage::CreateAllowFileAccessOptions()); filesystem_ = isolated_context()->RegisterFileSystemForPath( - storage::kFileSystemTypeNativeMedia, std::string(), root_path(), + storage::kFileSystemTypeLocalMedia, std::string(), root_path(), nullptr); filesystem_id_ = filesystem_.id(); } @@ -177,7 +177,7 @@ GURL origin() { return GURL("http://example.com"); } - storage::FileSystemType type() { return storage::kFileSystemTypeNativeMedia; } + storage::FileSystemType type() { return storage::kFileSystemTypeLocalMedia; } storage::FileSystemOperationRunner* operation_runner() { return file_system_context_->operation_runner();
diff --git a/chrome/browser/media_galleries/media_file_system_registry.cc b/chrome/browser/media_galleries/media_file_system_registry.cc index 1080b77..0c8bc43 100644 --- a/chrome/browser/media_galleries/media_file_system_registry.cc +++ b/chrome/browser/media_galleries/media_file_system_registry.cc
@@ -699,7 +699,7 @@ CHECK(!path.ReferencesParent()); return ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - fs_name, storage::kFileSystemTypeNativeMedia, + fs_name, storage::kFileSystemTypeLocalMedia, storage::FileSystemMountOption(), path); }
diff --git a/chrome/browser/payments/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestFactoryTest.java b/chrome/browser/payments/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestFactoryTest.java index c6e39b76..74844d80 100644 --- a/chrome/browser/payments/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestFactoryTest.java +++ b/chrome/browser/payments/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestFactoryTest.java
@@ -27,9 +27,8 @@ import org.chromium.content_public.browser.FeaturePolicyFeature; import org.chromium.content_public.browser.RenderFrameHost; import org.chromium.content_public.browser.WebContents; -import org.chromium.services.service_manager.InterfaceProvider; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; /** A test for ChromePaymentRequestFactory. */ @RunWith(BaseRobolectricTestRunner.class) @@ -82,19 +81,18 @@ @Test @Feature({"Payments"}) - public void testDisabledPolicyCausesNullReturn() { + public void testDisabledPolicyCausesBadMessage() { setPaymentFeaturePolicy(false); - InterfaceProvider provider = Mockito.mock(InterfaceProvider.class); - Mockito.doReturn(provider).when(mRenderFrameHost).getRemoteInterfaces(); - AtomicBoolean isConnectionError = new AtomicBoolean(false); - Mockito.doAnswer((args) -> { - isConnectionError.set(true); + AtomicInteger isKilledReason = new AtomicInteger(0); + Mockito.doAnswer(invocation -> { + isKilledReason.set((int) invocation.getArguments()[0]); return null; }) - .when(provider) - .onConnectionError(Mockito.any()); + .when(mRenderFrameHost) + .terminateRendererDueToBadMessage(Mockito.anyInt()); Assert.assertNull(createFactory(mRenderFrameHost).createImpl()); - Assert.assertTrue(isConnectionError.get()); + // 241 == PAYMENTS_WITHOUT_PERMISSION. + Assert.assertEquals(isKilledReason.get(), 241); } @Test
diff --git a/chrome/browser/platform_util_unittest.cc b/chrome/browser/platform_util_unittest.cc index b093629..ecd0bc1 100644 --- a/chrome/browser/platform_util_unittest.cc +++ b/chrome/browser/platform_util_unittest.cc
@@ -81,7 +81,7 @@ // The test_directory needs to be mounted for it to be accessible. content::BrowserContext::GetMountPoints(GetProfile()) - ->RegisterFileSystem("test", storage::kFileSystemTypeNativeLocal, + ->RegisterFileSystem("test", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), test_directory); // To test opening a file, we are going to register a mock extension that
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java index eba8962b..20ae65d 100644 --- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java +++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -515,6 +515,12 @@ public static final String OFFLINE_INDICATOR_V2_ENABLED = "offline_indicator_v2_enabled"; /** + * The wall time of when the offline indicator was shown in milliseconds. + */ + public static final String OFFLINE_INDICATOR_V2_WALL_TIME_SHOWN_MS = + "Chrome.OfflineIndicatorV2.WallTimeShownMs"; + + /** * The measurement interval (in minutes) used to schedule the currently running * OfflineMeasureBackgroundTask. This value is zero if the OfflineMeasureBackgroundTask is not * currently running. @@ -873,6 +879,7 @@ IMAGE_DESCRIPTIONS_JUST_ONCE_COUNT, IMAGE_DESCRIPTIONS_DONT_ASK_AGAIN, ISOLATED_SPLITS_DEX_COMPILE_VERSION, + OFFLINE_INDICATOR_V2_WALL_TIME_SHOWN_MS, OFFLINE_MEASUREMENTS_CURRENT_TASK_MEASUREMENT_INTERVAL_IN_MINUTES, OFFLINE_MEASUREMENTS_LAST_CHECK_MILLIS, OFFLINE_MEASUREMENTS_TIME_BETWEEN_CHECKS_MILLIS_LIST,
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc index 6788f985..353400f 100644 --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc
@@ -1044,7 +1044,7 @@ chromeos::RegisterSamlProfilePrefs(registry); chromeos::ScreenTimeController::RegisterProfilePrefs(registry); SecondaryAccountConsentLogger::RegisterPrefs(registry); - chromeos::EduCoexistenceConsentInvalidationController::RegisterProfilePrefs( + ash::EduCoexistenceConsentInvalidationController::RegisterProfilePrefs( registry); SigninErrorNotifier::RegisterPrefs(registry); chromeos::ServicesCustomizationDocument::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/prefs/pref_service_incognito_allowlist.cc b/chrome/browser/prefs/pref_service_incognito_allowlist.cc index 449610c2..df35458 100644 --- a/chrome/browser/prefs/pref_service_incognito_allowlist.cc +++ b/chrome/browser/prefs/pref_service_incognito_allowlist.cc
@@ -41,6 +41,7 @@ ash::prefs::kAccessibilityScreenMagnifierCenterFocus, ash::prefs::kAccessibilityScreenMagnifierEnabled, ash::prefs::kAccessibilityScreenMagnifierFocusFollowingEnabled, + ash::prefs::kAccessibilityScreenMagnifierMousePanningMode, ash::prefs::kAccessibilityScreenMagnifierScale, ash::prefs::kAccessibilityVirtualKeyboardEnabled, ash::prefs::kAccessibilityMonoAudioEnabled,
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc index 40e89a1..6448724 100644 --- a/chrome/browser/profiles/profile_impl.cc +++ b/chrome/browser/profiles/profile_impl.cc
@@ -532,14 +532,14 @@ #if BUILDFLAG(IS_CHROMEOS_ASH) if (is_regular_profile) { - // |chromeos::InitializeAccountManager| is called during a User's session + // |ash::InitializeAccountManager| is called during a User's session // initialization but some tests do not properly login to a User Session. - // This invocation of |chromeos::InitializeAccountManager| is used only - // during tests. - // Note: |chromeos::InitializeAccountManager| is idempotent and safe to call + // This invocation of |ash::InitializeAccountManager| is used only during + // tests. + // Note: |ash::InitializeAccountManager| is idempotent and safe to call // multiple times. // TODO(https://crbug.com/982233): Remove this call. - chromeos::InitializeAccountManager( + ash::InitializeAccountManager( path_, base::DoNothing() /* initialization_callback */); chromeos::AccountManager* account_manager =
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc index a7930ea..701ecf8a 100644 --- a/chrome/browser/profiles/profile_manager.cc +++ b/chrome/browser/profiles/profile_manager.cc
@@ -1114,11 +1114,11 @@ } profile->GetPrefs()->SetInteger(arc::prefs::kArcSupervisionTransition, static_cast<int>(supervisionTransition)); - chromeos::ChildAccountTypeChangedUserData::GetForProfile(profile) - ->SetValue(true); + ash::ChildAccountTypeChangedUserData::GetForProfile(profile)->SetValue( + true); } else { - chromeos::ChildAccountTypeChangedUserData::GetForProfile(profile) - ->SetValue(false); + ash::ChildAccountTypeChangedUserData::GetForProfile(profile)->SetValue( + false); } if (user_is_child) { @@ -1475,8 +1475,7 @@ AccessibilityLabelsServiceFactory::GetForProfile(profile)->Init(); #if BUILDFLAG(IS_CHROMEOS_ASH) - chromeos::AccountManagerPolicyControllerFactory::GetForBrowserContext( - profile); + ash::AccountManagerPolicyControllerFactory::GetForBrowserContext(profile); #endif // Creates the LiteVideo Keyed Service and begins loading the
diff --git a/chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.cc b/chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.cc index 0a04a1e..e90525f 100644 --- a/chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.cc +++ b/chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.cc
@@ -112,9 +112,7 @@ // First level directory for isolated filesystem to lookup. std::string kFirstLevelDirectory("crxfs"); return storage::IsolatedContext::GetInstance()->RegisterFileSystemForPath( - storage::kFileSystemTypeNativeLocal, - std::string(), - extension->path(), + storage::kFileSystemTypeLocal, std::string(), extension->path(), &kFirstLevelDirectory); #else return storage::IsolatedContext::ScopedFSHandle();
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn index 92f7b29b..522c77d0 100644 --- a/chrome/browser/resources/settings/chromeos/BUILD.gn +++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -554,8 +554,6 @@ "chromeos/crostini_page/crostini_port_forwarding.js", "chromeos/crostini_page/crostini_port_forwarding_add_port_dialog.html", "chromeos/crostini_page/crostini_port_forwarding_add_port_dialog.js", - "chromeos/crostini_page/crostini_shared_paths.html", - "chromeos/crostini_page/crostini_shared_paths.js", "chromeos/crostini_page/crostini_subpage.html", "chromeos/crostini_page/crostini_subpage.js", "chromeos/date_time_page/date_time_page.html", @@ -607,6 +605,8 @@ "chromeos/google_assistant_page/google_assistant_page.js", "chromeos/guest_os/guest_os_browser_proxy.html", "chromeos/guest_os/guest_os_browser_proxy.js", + "chromeos/guest_os/guest_os_shared_paths.html", + "chromeos/guest_os/guest_os_shared_paths.js", "chromeos/guest_os/guest_os_shared_usb_devices.html", "chromeos/guest_os/guest_os_shared_usb_devices.js", "chromeos/internet_page/esim_remove_profile_dialog.js", @@ -769,8 +769,6 @@ "chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_browser_proxy.js", "chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.html", "chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.js", - "chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_shared_paths.html", - "chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_shared_paths.js", "chromeos/os_apps_page/app_management_page/pwa_detail_view.html", "chromeos/os_apps_page/app_management_page/pwa_detail_view.js", "chromeos/os_apps_page/app_management_page/reducers.html",
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn index 52648002..cf0c37a9 100644 --- a/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn +++ b/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn
@@ -14,7 +14,6 @@ ":crostini_page", ":crostini_port_forwarding", ":crostini_port_forwarding_add_port_dialog", - ":crostini_shared_paths", ":crostini_subpage", ] } @@ -34,9 +33,9 @@ js_library("crostini_browser_proxy") { deps = [ + "//ui/webui/resources/cr_elements/cr_input:cr_input", "//ui/webui/resources/js:cr", "//ui/webui/resources/js:load_time_data", - "//ui/webui/resources/cr_elements/cr_input:cr_input", ] } @@ -69,15 +68,6 @@ externs_list = [ "$externs_path/settings_private.js" ] } -js_library("crostini_shared_paths") { - deps = [ - "..:metrics_recorder", - "..:os_route", - "../../prefs:prefs_behavior", - "../guest_os:guest_os_browser_proxy", - ] -} - js_library("crostini_port_forwarding") { deps = [ ":crostini_browser_proxy", @@ -129,8 +119,6 @@ ":crostini_port_forwarding.m", ":crostini_port_forwarding_add_port_dialog.m", - # ":crostini_shared_paths.m", - # ":crostini_shared_usb_devices.m", # ":crostini_subpage.m" ] } @@ -164,9 +152,9 @@ js_library("crostini_browser_proxy.m") { sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/crostini_page/crostini_browser_proxy.m.js" ] deps = [ + "//ui/webui/resources/cr_elements/cr_input:cr_input.m", "//ui/webui/resources/js:cr.m", "//ui/webui/resources/js:load_time_data.m", - "//ui/webui/resources/cr_elements/cr_input:cr_input.m", ] extra_deps = [ ":modulize" ] } @@ -277,14 +265,6 @@ extra_deps = [ ":crostini_port_forwarding_add_port_dialog_module" ] } -js_library("crostini_shared_paths.m") { - sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/crostini_page/crostini_shared_paths.m.js" ] - deps = [ - # TODO: Fill those in. - ] - extra_deps = [ ":crostini_shared_paths_module" ] -} - js_library("crostini_subpage.m") { sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.m.js" ] deps = [ @@ -307,7 +287,6 @@ ":crostini_page_module", ":crostini_port_forwarding_add_port_dialog_module", ":crostini_port_forwarding_module", - ":crostini_shared_paths_module", ":crostini_subpage_module", ":modulize", ] @@ -391,12 +370,6 @@ namespace_rewrites = os_settings_namespace_rewrites } -polymer_modulizer("crostini_shared_paths") { - js_file = "crostini_shared_paths.js" - html_file = "crostini_shared_paths.html" - html_type = "dom-module" -} - polymer_modulizer("crostini_subpage") { js_file = "crostini_subpage.js" html_file = "crostini_subpage.html"
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_browser_proxy.js b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_browser_proxy.js index a37d1152..2fc9496 100644 --- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_browser_proxy.js +++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_browser_proxy.js
@@ -23,20 +23,6 @@ }; /** - * @typedef {{path: string, - * pathDisplayText: string}} - */ -/* #export */ let CrostiniSharedPath; - -/** - * @typedef {{label: string, - * guid: string, - * shared: boolean, - * shareWillReassign: boolean}} - */ -/* #export */ let CrostiniSharedUsbDevice; - -/** * @typedef {{label: string, * port_number: number, * protocol_type: !CrostiniPortProtocol}} @@ -89,31 +75,6 @@ requestRemoveCrostini() {} /** - * @param {!Array<string>} paths Paths to sanitze. - * @return {!Promise<!Array<string>>} Text to display in UI. - */ - getCrostiniSharedPathsDisplayText(paths) {} - - /** - * Called when page is ready. - * @return {!Promise<boolean>} - */ - notifyCrostiniSharedUsbDevicesPageReady() {} - - /** - * @param {string} guid Unique device identifier. - * @param {boolean} shared Whether device is currently shared with Crostini. - */ - setCrostiniUsbDeviceShared(guid, shared) {} - - /** - * @param {string} vmName VM to stop sharing path with. - * @param {string} path Path to stop sharing. - * @return {!Promise<boolean>} Result of unsharing. - */ - removeCrostiniSharedPath(vmName, path) {} - - /** * Request chrome send a crostini-installer-status-changed event with the * current installer status */ @@ -286,26 +247,6 @@ } /** @override */ - getCrostiniSharedPathsDisplayText(paths) { - return cr.sendWithPromise('getCrostiniSharedPathsDisplayText', paths); - } - - /** @override */ - notifyCrostiniSharedUsbDevicesPageReady() { - return cr.sendWithPromise('notifyCrostiniSharedUsbDevicesPageReady'); - } - - /** @override */ - setCrostiniUsbDeviceShared(guid, shared) { - return chrome.send('setCrostiniUsbDeviceShared', [guid, shared]); - } - - /** @override */ - removeCrostiniSharedPath(vmName, path) { - return cr.sendWithPromise('removeCrostiniSharedPath', vmName, path); - } - - /** @override */ requestCrostiniInstallerStatus() { chrome.send('requestCrostiniInstallerStatus'); }
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.html b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.html index fc573526..5dc6efd9 100644 --- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.html +++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.html
@@ -12,13 +12,13 @@ <link rel="import" href="../../settings_page/settings_animated_pages.html"> <link rel="import" href="../../settings_page/settings_subpage.html"> <link rel="import" href="../../settings_shared_css.html"> +<link rel="import" href="../guest_os/guest_os_shared_paths.html"> <link rel="import" href="../guest_os/guest_os_shared_usb_devices.html"> <link rel="import" href="../localized_link/localized_link.html"> <link rel="import" href="crostini_arc_adb.html"> <link rel="import" href="crostini_browser_proxy.html"> <link rel="import" href="crostini_export_import.html"> <link rel="import" href="crostini_port_forwarding.html"> -<link rel="import" href="crostini_shared_paths.html"> <link rel="import" href="crostini_subpage.html"> <dom-module id="settings-crostini-page"> @@ -103,9 +103,10 @@ <template is="dom-if" route-path="/crostini/sharedPaths"> <settings-subpage associated-control="[[$$('#crostini')]]" - page-title="$i18n{crostiniSharedPaths}"> - <settings-crostini-shared-paths prefs="{{prefs}}"> - </settings-crostini-shared-paths> + page-title="$i18n{guestOsSharedPaths}"> + <settings-guest-os-shared-paths + guest-os-type="crostini" prefs="{{prefs}}"> + </settings-guest-os-shared-paths> </settings-subpage> </template>
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_shared_paths.html b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_shared_paths.html deleted file mode 100644 index 7046927..0000000 --- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_shared_paths.html +++ /dev/null
@@ -1,74 +0,0 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> - -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> -<link rel="import" href="../guest_os/guest_os_browser_proxy.html"> -<link rel="import" href="../../i18n_setup.html"> -<link rel="import" href="../../prefs/prefs_behavior.html"> -<link rel="import" href="../../settings_shared_css.html"> -<link rel="import" href="../metrics_recorder.html"> - -<dom-module id="settings-crostini-shared-paths"> - <template> - <style include="settings-shared"></style> - <div class="settings-box first"> - <div role="text"> - $i18n{crostiniSharedPathsInstructionsLocate} - $i18n{crostiniSharedPathsInstructionsAdd} - <span id="crostiniInstructionsRemove" - hidden="[[!sharedPaths_.length]]"> - $i18n{crostiniSharedPathsInstructionsRemove} - </span> - </div> - </div> - <div id="crostiniListEmpty" class="settings-box secondary continuation" - hidden="[[sharedPaths_.length]]" > - $i18n{crostiniSharedPathsListEmptyMessage} - </div> - <div id="crostiniList" hidden="[[!sharedPaths_.length]]"> - <div class="settings-box continuation"> - <h2 id="crostiniListHeading" class="start"> - $i18n{crostiniSharedPathsListHeading} - </h2> - </div> - <iron-list class="list-frame vertical-list" role="list" - aria-labeledby="crostiniListHeading" items="[[sharedPaths_]]"> - <template> - <div class="list-item" role="listitem"> - <div class="start" aria-hidden="true" - id="[[generatePathDisplayTextId_(index)]]"> - [[item.pathDisplayText]] - </div> - <cr-icon-button class="icon-clear" tabindex$="[[tabIndex]]" - on-click="onRemoveSharedPathTap_" - title="$i18n{crostiniSharedPathsRemoveSharing}" - aria-labeledby$="[[generatePathDisplayTextId_(index)]]"> - </cr-icon-button> - </div> - </template> - </iron-list> - </div> - <template is="dom-if" if="[[sharedPathWhichFailedRemoval_]]" restamp> - <cr-dialog id="removeSharedPathFailedDialog" close-text="$i18n{close}"> - <div slot="title"> - $i18n{crostiniSharedPathsRemoveFailureDialogTitle} - </div> - <div slot="body"> - $i18n{crostiniSharedPathsRemoveFailureDialogMessage} - </div> - <div slot="button-container"> - <cr-button id="cancel" class="cancel-button" - on-click="onRemoveFailedDismissTap_"> - $i18n{ok} - </cr-button> - <cr-button id="retry" class="action-button" - on-click="onRemoveFailedRetryTap_"> - $i18n{crostiniSharedPathsRemoveFailureTryAgain} - </cr-button> - </div> - </cr-dialog> - </template> - </template> - <script src="crostini_shared_paths.js"></script> -</dom-module>
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_shared_paths.js b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_shared_paths.js deleted file mode 100644 index 1119b95..0000000 --- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_shared_paths.js +++ /dev/null
@@ -1,124 +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. - -/** - * @fileoverview - * 'crostini-shared-paths' is the settings shared paths subpage for Crostini. - */ - -(function() { - -/** - * The default crostini VM is named 'termina'. - * https://cs.chromium.org/chromium/src/chrome/browser/chromeos/crostini/crostini_util.h?q=kCrostiniDefaultVmName&dr=CSs - * @type {string} - */ -const DEFAULT_CROSTINI_VM = 'termina'; - -Polymer({ - is: 'settings-crostini-shared-paths', - - behaviors: [PrefsBehavior], - - properties: { - /** Preferences state. */ - prefs: { - type: Object, - notify: true, - }, - - /** - * The shared path string suitable for display in the UI. - * @private {Array<!{path: string, pathDisplayText: string}>} - */ - sharedPaths_: Array, - - /** - * The shared path which failed to be removed in the most recent attempt - * to remove a path. Null indicates that removal succeeded. - * @private {?string} - */ - sharedPathWhichFailedRemoval_: { - type: String, - value: null, - }, - }, - - observers: [ - 'onGuestOsSharedPathsChanged_(prefs.guest_os.paths_shared_to_vms.value)' - ], - - /** - * @param {!Object<!Array<string>>} paths - * @private - */ - onGuestOsSharedPathsChanged_(paths) { - const vmPaths = []; - for (const path in paths) { - const vms = paths[path]; - if (vms.includes(DEFAULT_CROSTINI_VM)) { - vmPaths.push(path); - } - } - settings.GuestOsBrowserProxyImpl.getInstance() - .getGuestOsSharedPathsDisplayText(vmPaths) - .then(text => { - this.sharedPaths_ = vmPaths.map( - (path, i) => ({path: path, pathDisplayText: text[i]})); - }); - }, - - /** - * @param {string} path - * @private - */ - removeSharedPath_(path) { - this.sharedPathWhichFailedRemoval_ = null; - settings.GuestOsBrowserProxyImpl.getInstance() - .removeGuestOsSharedPath(DEFAULT_CROSTINI_VM, path) - .then(result => { - if (!result) { - this.sharedPathWhichFailedRemoval_ = path; - // Flush to make sure that the retry dialog is attached. - Polymer.dom.flush(); - this.$$('#removeSharedPathFailedDialog').showModal(); - } - }); - settings.recordSettingChange(); - }, - - /** - * @param {!Event} event - * @private - */ - onRemoveSharedPathTap_(event) { - this.removeSharedPath_(event.model.item.path); - }, - - /** - * @param {!Event} event - * @private - */ - onRemoveFailedRetryTap_(event) { - this.removeSharedPath_(assert(this.sharedPathWhichFailedRemoval_)); - }, - - /** - * @param {!Event} event - * @private - */ - onRemoveFailedDismissTap_(event) { - this.sharedPathWhichFailedRemoval_ = null; - }, - - /** - * @param {number} index - * @return {string} - * @private - */ - generatePathDisplayTextId_(index) { - return 'path-display-text-' + index; - }, -}); -})();
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.html b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.html index 035296f..392e0d5 100644 --- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.html +++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.html
@@ -46,7 +46,7 @@ <cr-link-row class="hr" id="crostini-shared-paths" - label="$i18n{crostiniSharedPaths}" + label="$i18n{guestOsSharedPaths}" on-click="onSharedPathsClick_" role-description="$i18n{subpageArrowRoleDescription}"> </cr-link-row>
diff --git a/chrome/browser/resources/settings/chromeos/guest_os/BUILD.gn b/chrome/browser/resources/settings/chromeos/guest_os/BUILD.gn index d76940d..464f4e1 100644 --- a/chrome/browser/resources/settings/chromeos/guest_os/BUILD.gn +++ b/chrome/browser/resources/settings/chromeos/guest_os/BUILD.gn
@@ -10,6 +10,7 @@ js_type_check("closure_compile") { deps = [ ":guest_os_browser_proxy", + ":guest_os_shared_paths", ":guest_os_shared_usb_devices", ] } @@ -18,6 +19,13 @@ deps = [ "//ui/webui/resources/js:cr" ] } +js_library("guest_os_shared_paths") { + deps = [ + ":guest_os_browser_proxy", + "..:metrics_recorder", + ] +} + js_library("guest_os_shared_usb_devices") { deps = [ ":guest_os_browser_proxy", @@ -47,13 +55,28 @@ extra_deps = [ ":guest_os_shared_usb_devices_module" ] } +js_library("guest_os_shared_paths.m") { + sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/guest_os/guest_os_shared_paths.m.js" ] + deps = [ + # TODO: Fill those in. + ] + extra_deps = [ ":guest_os_shared_paths_module" ] +} + group("polymer3_elements") { public_deps = [ + ":guest_os_shared_paths_module", ":guest_os_shared_usb_devices_module", ":modulize", ] } +polymer_modulizer("guest_os_shared_paths") { + js_file = "guest_os_shared_paths.js" + html_file = "guest_os_shared_paths.html" + html_type = "dom-module" +} + polymer_modulizer("guest_os_shared_usb_devices") { js_file = "guest_os_shared_usb_devices.js" html_file = "guest_os_shared_usb_devices.html"
diff --git a/chrome/browser/resources/settings/chromeos/guest_os/guest_os_shared_paths.html b/chrome/browser/resources/settings/chromeos/guest_os/guest_os_shared_paths.html new file mode 100644 index 0000000..6b97f61 --- /dev/null +++ b/chrome/browser/resources/settings/chromeos/guest_os/guest_os_shared_paths.html
@@ -0,0 +1,70 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> +<link rel="import" href="guest_os_browser_proxy.html"> +<link rel="import" href="../../settings_shared_css.html"> +<link rel="import" href="../metrics_recorder.html"> + +<dom-module id="settings-guest-os-shared-paths"> + <template> + <style include="settings-shared"></style> + <div class="settings-box first"> + <div role="text"> + [[getDescriptionText_()]] + <span id="guestOsInstructionsRemove" hidden="[[!sharedPaths_.length]]"> + $i18n{guestOsSharedPathsInstructionsRemove} + </span> + </div> + </div> + <div id="guestOsListEmpty" class="settings-box secondary continuation" + hidden="[[sharedPaths_.length]]" > + $i18n{guestOsSharedPathsListEmptyMessage} + </div> + <div id="guestOsList" hidden="[[!sharedPaths_.length]]"> + <div class="settings-box continuation"> + <h2 id="guestOsListHeading" class="start"> + $i18n{guestOsSharedPathsListHeading} + </h2> + </div> + <iron-list class="list-frame vertical-list" role="list" + aria-labeledby="guestOsListHeading" items="[[sharedPaths_]]"> + <template> + <div class="list-item" role="listitem"> + <div class="start" aria-hidden="true" + id="[[generatePathDisplayTextId_(index)]]"> + [[item.pathDisplayText]] + </div> + <cr-icon-button class="icon-clear" tabindex$="[[tabIndex]]" + on-click="onRemoveSharedPathClick_" + title="$i18n{guestOsSharedPathsStopSharing}" + aria-labeledby$="[[generatePathDisplayTextId_(index)]]"> + </cr-icon-button> + </div> + </template> + </iron-list> + </div> + <template is="dom-if" if="[[sharedPathWhichFailedRemoval_]]" restamp> + <cr-dialog id="removeSharedPathFailedDialog" close-text="$i18n{close}" + show-on-attach> + <div slot="title"> + $i18n{guestOsSharedPathsRemoveFailureDialogTitle} + </div> + <div slot="body"> + [[getRemoveFailureMessage_()]] + </div> + <div slot="button-container"> + <cr-button id="cancel" class="cancel-button" + on-click="onRemoveFailedDismissClick_"> + $i18n{ok} + </cr-button> + <cr-button id="retry" class="action-button" + on-click="onRemoveFailedRetryClick_"> + $i18n{guestOsSharedPathsRemoveFailureTryAgain} + </cr-button> + </div> + </cr-dialog> + </template> + </template> + <script src="guest_os_shared_paths.js"></script> +</dom-module>
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_shared_paths.js b/chrome/browser/resources/settings/chromeos/guest_os/guest_os_shared_paths.js similarity index 62% rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_shared_paths.js rename to chrome/browser/resources/settings/chromeos/guest_os/guest_os_shared_paths.js index c26f6a05..0ff456a 100644 --- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_shared_paths.js +++ b/chrome/browser/resources/settings/chromeos/guest_os/guest_os_shared_paths.js
@@ -4,22 +4,25 @@ /** * @fileoverview - * 'plugin-vm-shared-paths' is the settings shared paths subpage for Plugin VM. + * 'guest-os-shared-paths' is the settings shared paths subpage for guest OSes. */ (function() { -/** - * The Plugin VM is named 'PvmDefault'. - * https://cs.chromium.org/chromium/src/chrome/browser/chromeos/plugin_vm/plugin_vm_util.h?q=kPluginVmName - * @type {string} - */ -const PLUGIN_VM = 'PvmDefault'; +const CROSTINI_TYPE = 'crostini'; +const PLUGIN_VM_TYPE = 'pluginVm'; Polymer({ - is: 'settings-plugin-vm-shared-paths', + is: 'settings-guest-os-shared-paths', + + behaviors: [I18nBehavior], properties: { + /** + * The type of Guest OS to share with. Should be 'crostini' or 'pluginVm'. + */ + guestOsType: String, + /** Preferences state. */ prefs: { type: Object, @@ -56,7 +59,7 @@ const vmPaths = []; for (const path in paths) { const vms = paths[path]; - if (vms.includes(PLUGIN_VM)) { + if (vms.includes(this.vmName_())) { vmPaths.push(path); } } @@ -75,7 +78,7 @@ removeSharedPath_(path) { this.sharedPathWhichFailedRemoval_ = null; settings.GuestOsBrowserProxyImpl.getInstance() - .removeGuestOsSharedPath(PLUGIN_VM, path) + .removeGuestOsSharedPath(this.vmName_(), path) .then(success => { if (!success) { this.sharedPathWhichFailedRemoval_ = path; @@ -101,5 +104,40 @@ onRemoveFailedDismissClick_() { this.sharedPathWhichFailedRemoval_ = null; }, + + /** + * @return {string} The name of the VM to share devices with. + * @private + */ + vmName_() { + return {crostini: 'termina', pluginVm: 'PvmDefault'}[this.guestOsType]; + }, + + /** + * @return {string} Description for the page. + * @private + */ + getDescriptionText_() { + return this.i18n(this.guestOsType + 'SharedPathsInstructionsLocate') + + '\n' + this.i18n(this.guestOsType + 'SharedPathsInstructionsAdd'); + }, + + /** + * @return {string} Message to display when removing a shared path fails. + * @private + */ + getRemoveFailureMessage_() { + return this.i18n( + this.guestOsType + 'SharedPathsRemoveFailureDialogMessage'); + }, + + /** + * @param {number} index + * @return {string} + * @private + */ + generatePathDisplayTextId_(index) { + return 'path-display-text-' + index; + }, }); })();
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/BUILD.gn index 33562021..d27c07ed 100644 --- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/BUILD.gn +++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/BUILD.gn
@@ -8,7 +8,6 @@ deps = [ ":plugin_vm_browser_proxy", ":plugin_vm_detail_view", - ":plugin_vm_shared_paths", ] } @@ -27,22 +26,12 @@ ] } -js_library("plugin_vm_shared_paths") { - deps = [ - "../../..:metrics_recorder", - "../../../guest_os:guest_os_browser_proxy", - "//ui/webui/resources/js:i18n_behavior", - "//ui/webui/resources/js:web_ui_listener_behavior", - ] -} - # TODO: Uncomment as the Polymer3 migration makes progress. #js_type_check("closure_compile_module") { # is_polymer3 = true # deps = [ # ":plugin_vm_browser_proxy.m", # ":plugin_vm_detail_view.m", -# ":plugin_vm_shared_paths.m", # ] #} @@ -62,21 +51,12 @@ extra_deps = [ ":plugin_vm_detail_view_module" ] } -js_library("plugin_vm_shared_paths.m") { - sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_shared_paths.m.js" ] - deps = [ - # TODO: Fill those in. - ] - extra_deps = [ ":plugin_vm_shared_paths_module" ] -} - import("//tools/polymer/polymer.gni") group("polymer3_elements") { public_deps = [ ":modulize", ":plugin_vm_detail_view_module", - ":plugin_vm_shared_paths_module", ] } @@ -86,12 +66,6 @@ html_type = "dom-module" } -polymer_modulizer("plugin_vm_shared_paths") { - js_file = "plugin_vm_shared_paths.js" - html_file = "plugin_vm_shared_paths.html" - html_type = "dom-module" -} - import("//ui/webui/resources/tools/js_modulizer.gni") js_modulizer("modulize") {
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.html index 48998ea..2c8c098 100644 --- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.html +++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.html
@@ -57,7 +57,7 @@ <div class="permission-card-row separated-row header-text clickable" on-click="onSharedPathsClick_"> <div id="sharedPathsLabel" class="header-text" aria-hidden="true"> - $i18n{pluginVmSharedPaths} + $i18n{guestOsSharedPaths} </div> <div class="permission-row-controls"> <cr-icon-button class="subpage-arrow app-management-item-arrow"
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_shared_paths.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_shared_paths.html deleted file mode 100644 index 02f28af88..0000000 --- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_shared_paths.html +++ /dev/null
@@ -1,68 +0,0 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> - -<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> -<link rel="import" href="../../../guest_os/guest_os_browser_proxy.html"> -<link rel="import" href="../../../../settings_shared_css.html"> -<link rel="import" href="../../../metrics_recorder.html"> - -<dom-module id="settings-plugin-vm-shared-paths"> - <template> - <style include="settings-shared"></style> - <div class="settings-box first"> - <div role="text"> - $i18n{pluginVmSharedPathsInstructionsLocate} - $i18n{pluginVmSharedPathsInstructionsAdd} - <span id="pluginVmInstructionsRemove" - hidden="[[!sharedPaths_.length]]"> - $i18n{pluginVmSharedPathsInstructionsRemove} - </span> - </div> - </div> - <div id="pluginVmListEmpty" class="settings-box secondary continuation" - hidden="[[sharedPaths_.length]]" > - $i18n{pluginVmSharedPathsListEmptyMessage} - </div> - <div id="pluginVmList" hidden="[[!sharedPaths_.length]]"> - <div class="settings-box continuation"> - <h2 id="pluginVmListHeading" class="start"> - $i18n{pluginVmSharedPathsListHeading} - </h2> - </div> - <iron-list class="list-frame vertical-list" role="list" - aria-labeledby="pluginVmListHeading" items="[[sharedPaths_]]"> - <template> - <div class="list-item" role="listitem"> - <div class="start">[[item.pathDisplayText]]</div> - <cr-icon-button class="icon-clear" tabindex$="[[tabIndex]]" - on-click="onRemoveSharedPathClick_" - title="$i18n{pluginVmSharedPathsRemoveSharing}"> - </cr-icon-button> - </div> - </template> - </iron-list> - </div> - <template is="dom-if" if="[[sharedPathWhichFailedRemoval_]]" restamp> - <cr-dialog id="removeSharedPathFailedDialog" close-text="$i18n{close}" - show-on-attach> - <div slot="title"> - $i18n{pluginVmSharedPathsRemoveFailureDialogTitle} - </div> - <div slot="body"> - $i18n{pluginVmSharedPathsRemoveFailureDialogMessage} - </div> - <div slot="button-container"> - <cr-button id="cancel" class="cancel-button" - on-click="onRemoveFailedDismissClick_"> - $i18n{ok} - </cr-button> - <cr-button id="retry" class="action-button" - on-click="onRemoveFailedRetryClick_"> - $i18n{pluginVmSharedPathsRemoveFailureTryAgain} - </cr-button> - </div> - </cr-dialog> - </template> - </template> - <script src="plugin_vm_shared_paths.js"></script> -</dom-module>
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.html b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.html index 346efc0..196400c 100644 --- a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.html +++ b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.html
@@ -15,12 +15,12 @@ <link rel="import" href="../../settings_shared_css.html"> <link rel="import" href="../../settings_shared_css.html"> <link rel="import" href="../guest_os/guest_os_shared_usb_devices.html"> +<link rel="import" href="../guest_os/guest_os_shared_paths.html"> <link rel="import" href="../localized_link/localized_link.html"> <link rel="import" href="android_apps_browser_proxy.html"> <link rel="import" href="android_apps_subpage.html"> <link rel="import" href="app_management_page/app_management_page.html"> <link rel="import" href="app_management_page/app_detail_view.html"> -<link rel="import" href="app_management_page/plugin_vm_page/plugin_vm_shared_paths.html"> <link rel="import" href="app_management_page/uninstall_button.html"> <dom-module id="os-settings-apps-page"> @@ -122,9 +122,10 @@ <!-- Plugin VM --> <template is="dom-if" if="[[showPluginVm]]" restamp> <template is="dom-if" route-path="/app-management/pluginVm/sharedPaths"> - <settings-subpage page-title="$i18n{pluginVmSharedPaths}"> - <settings-plugin-vm-shared-paths prefs="{{prefs}}"> - </settings-plugin-vm-shared-paths> + <settings-subpage page-title="$i18n{guestOsSharedPaths}"> + <settings-guest-os-shared-paths + guest-os-type="pluginVm" prefs="{{prefs}}"> + </settings-guest-os-shared-paths> </settings-subpage> </template> <template is="dom-if"
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni index 898f5f4..fadcbc0 100644 --- a/chrome/browser/resources/settings/chromeos/os_settings.gni +++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -96,7 +96,7 @@ cr_elements_chromeos_auto_imports + [ "chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.html|AmbientModeBrowserProxy,AmbientModeBrowserProxyImpl", "chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.html|AmbientModeTopicSource,AmbientModeTemperatureUnit,AmbientModeAlbum,AmbientModeSettings,TopicSourceItem", - "chrome/browser/resources/settings/chromeos/crostini_page/crostini_browser_proxy.html|DEFAULT_CROSTINI_VM, DEFAULT_CROSTINI_CONTAINER, CrostiniPortProtocol, CrostiniSharedPath, CrostiniSharedUsbDevice, CrostiniPortSetting, CrostiniDiskInfo, CrostiniPortActiveSetting, CrostiniBrowserProxy, CrostiniBrowserProxyImpl,PortState,MIN_VALID_PORT_NUMBER, MAX_VALID_PORT_NUMBER", + "chrome/browser/resources/settings/chromeos/crostini_page/crostini_browser_proxy.html|DEFAULT_CROSTINI_VM, DEFAULT_CROSTINI_CONTAINER, CrostiniPortProtocol, CrostiniPortSetting, CrostiniDiskInfo, CrostiniPortActiveSetting, CrostiniBrowserProxy, CrostiniBrowserProxyImpl,PortState,MIN_VALID_PORT_NUMBER, MAX_VALID_PORT_NUMBER", "chrome/browser/resources/settings/chromeos/internet_page/cellular_setup_settings_delegate.html|CellularSetupSettingsDelegate", "chrome/browser/resources/settings/chromeos/internet_page/internet_page_browser_proxy.html|InternetPageBrowserProxy,InternetPageBrowserProxyImpl", "chrome/browser/resources/settings/chromeos/deep_linking_behavior.html|DeepLinkingBehavior",
diff --git a/chrome/browser/safe_browsing/chrome_password_protection_service.h b/chrome/browser/safe_browsing/chrome_password_protection_service.h index f92fd1a..4dc2560 100644 --- a/chrome/browser/safe_browsing/chrome_password_protection_service.h +++ b/chrome/browser/safe_browsing/chrome_password_protection_service.h
@@ -114,7 +114,7 @@ const std::string& verdict_token, ReusedPasswordAccountType password_type) override; - void ShowInterstitial(content::WebContents* web_contens, + void ShowInterstitial(content::WebContents* web_contents, ReusedPasswordAccountType password_type) override; // Called when user interacts with password protection UIs.
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinator.java index 1e9c60d..78e841e 100644 --- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinator.java +++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinator.java
@@ -15,7 +15,6 @@ import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.TabHidingType; import org.chromium.components.browser_ui.share.ShareParams; -import org.chromium.services.service_manager.InterfaceProvider; import org.chromium.ui.widget.Toast; import org.chromium.url.GURL; @@ -87,8 +86,8 @@ return; } - InterfaceProvider interfaces = mTab.getWebContents().getMainFrame().getRemoteInterfaces(); - mProducer = interfaces.getInterface(TextFragmentSelectorProducer.MANAGER); + mProducer = mTab.getWebContents().getMainFrame().getInterfaceToRendererFrame( + TextFragmentSelectorProducer.MANAGER); mProducer.generateSelector(new TextFragmentSelectorProducer.GenerateSelectorResponse() { @Override public void call(String selector) { @@ -134,4 +133,4 @@ mCancelRequest = true; mTab.removeObserver(this); } -} \ No newline at end of file +}
diff --git a/chrome/browser/signin/account_consistency_mode_manager.cc b/chrome/browser/signin/account_consistency_mode_manager.cc index 8f43f97..cffefa4b 100644 --- a/chrome/browser/signin/account_consistency_mode_manager.cc +++ b/chrome/browser/signin/account_consistency_mode_manager.cc
@@ -198,7 +198,7 @@ #endif #if BUILDFLAG(IS_CHROMEOS_ASH) - return chromeos::IsAccountManagerAvailable(profile) + return ash::IsAccountManagerAvailable(profile) ? AccountConsistencyMethod::kMirror : AccountConsistencyMethod::kDisabled; #endif
diff --git a/chrome/browser/signin/account_reconcilor_factory.cc b/chrome/browser/signin/account_reconcilor_factory.cc index aa8a6cf5..36bc499 100644 --- a/chrome/browser/signin/account_reconcilor_factory.cc +++ b/chrome/browser/signin/account_reconcilor_factory.cc
@@ -120,7 +120,7 @@ public: ChromeOSAccountReconcilorDelegate( signin::IdentityManager* identity_manager, - chromeos::AccountManagerMigrator* account_migrator) + ash::AccountManagerMigrator* account_migrator) : signin::MirrorAccountReconcilorDelegate(identity_manager), account_migrator_(account_migrator) {} ~ChromeOSAccountReconcilorDelegate() override = default; @@ -132,14 +132,14 @@ return false; } - const chromeos::AccountMigrationRunner::Status status = + const ash::AccountMigrationRunner::Status status = account_migrator_->GetStatus(); - return status != chromeos::AccountMigrationRunner::Status::kNotStarted && - status != chromeos::AccountMigrationRunner::Status::kRunning; + return status != ash::AccountMigrationRunner::Status::kNotStarted && + status != ash::AccountMigrationRunner::Status::kRunning; } // A non-owning pointer. - const chromeos::AccountManagerMigrator* const account_migrator_; + const ash::AccountManagerMigrator* const account_migrator_; DISALLOW_COPY_AND_ASSIGN(ChromeOSAccountReconcilorDelegate); }; @@ -211,7 +211,7 @@ // TODO(https://crbug.com/993317): Remove the check for // |IsAccountManagerAvailable| after fixing https://crbug.com/1008349 and // https://crbug.com/993317. - if (chromeos::IsAccountManagerAvailable(profile) && + if (ash::IsAccountManagerAvailable(profile) && chromeos::InstallAttributes::Get()->IsActiveDirectoryManaged()) { return std::make_unique< signin::ActiveDirectoryAccountReconcilorDelegate>(); @@ -229,8 +229,7 @@ // users have been migrated to Account Manager. return std::make_unique<ChromeOSAccountReconcilorDelegate>( IdentityManagerFactory::GetForProfile(profile), - chromeos::AccountManagerMigratorFactory::GetForBrowserContext( - profile)); + ash::AccountManagerMigratorFactory::GetForBrowserContext(profile)); #endif return std::make_unique<signin::MirrorAccountReconcilorDelegate>( IdentityManagerFactory::GetForProfile(profile));
diff --git a/chrome/browser/speech/speech_recognition_client_browser_interface.cc b/chrome/browser/speech/speech_recognition_client_browser_interface.cc index 47376428..726bb037 100644 --- a/chrome/browser/speech/speech_recognition_client_browser_interface.cc +++ b/chrome/browser/speech/speech_recognition_client_browser_interface.cc
@@ -11,6 +11,7 @@ #include "chrome/common/pref_names.h" #include "components/prefs/pref_change_registrar.h" #include "components/prefs/pref_service.h" +#include "media/base/media_switches.h" class PrefChangeRegistrar; @@ -62,7 +63,8 @@ bool enabled = profile_prefs_->GetBoolean(prefs::kLiveCaptionEnabled); if (enabled) { - if (speech::SodaInstaller::GetInstance()->IsSodaRegistered()) { + if (!base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption) || + speech::SodaInstaller::GetInstance()->IsSodaRegistered()) { NotifyObservers(enabled); } else { speech::SodaInstaller::GetInstance()->AddObserver(this);
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc index 0c6221a..d45a56d 100644 --- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc +++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
@@ -1440,13 +1440,13 @@ // Tests that items from an unmounted volume get removed from the holding space. TEST_F(HoldingSpaceKeyedServiceTest, RemoveItemsFromUnmountedVolumes) { auto test_mount_1 = std::make_unique<ScopedTestMountPoint>( - "test_mount_1", storage::kFileSystemTypeNativeLocal, + "test_mount_1", storage::kFileSystemTypeLocal, file_manager::VOLUME_TYPE_TESTING); test_mount_1->Mount(GetProfile()); HoldingSpaceModelAttachedWaiter(GetProfile()).Wait(); auto test_mount_2 = std::make_unique<ScopedTestMountPoint>( - "test_mount_2", storage::kFileSystemTypeNativeLocal, + "test_mount_2", storage::kFileSystemTypeLocal, file_manager::VOLUME_TYPE_TESTING); test_mount_2->Mount(GetProfile()); HoldingSpaceModelAttachedWaiter(GetProfile()).Wait();
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader_browsertest.cc b/chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader_browsertest.cc index d1e7d4c1..9b695cb 100644 --- a/chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader_browsertest.cc +++ b/chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader_browsertest.cc
@@ -57,8 +57,8 @@ return; storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - name_, storage::kFileSystemTypeNativeLocal, - storage::FileSystemMountOption(), temp_dir_.GetPath()); + name_, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), + temp_dir_.GetPath()); file_manager::util::GetFileSystemContextForExtensionId( profile, file_manager::kImageLoaderExtensionId) ->external_backend()
diff --git a/chrome/browser/ui/ash/holding_space/scoped_test_mount_point.cc b/chrome/browser/ui/ash/holding_space/scoped_test_mount_point.cc index fec4173d..66ea49f 100644 --- a/chrome/browser/ui/ash/holding_space/scoped_test_mount_point.cc +++ b/chrome/browser/ui/ash/holding_space/scoped_test_mount_point.cc
@@ -20,7 +20,7 @@ ScopedTestMountPoint::CreateAndMountDownloads(Profile* profile) { auto mount_point = std::make_unique<ScopedTestMountPoint>( file_manager::util::GetDownloadsMountPointName(profile), - storage::kFileSystemTypeNativeLocal, + storage::kFileSystemTypeLocal, file_manager::VOLUME_TYPE_DOWNLOADS_DIRECTORY); mount_point->Mount(profile); return mount_point;
diff --git a/chrome/browser/ui/screen_capture_notification_ui_stub.cc b/chrome/browser/ui/screen_capture_notification_ui_stub.cc index 3a61eb6..4f7a5e2 100644 --- a/chrome/browser/ui/screen_capture_notification_ui_stub.cc +++ b/chrome/browser/ui/screen_capture_notification_ui_stub.cc
@@ -17,10 +17,6 @@ NOTIMPLEMENTED(); return 0; } - - void SetStopCallback(base::OnceClosure stop_callback) override { - NOTIMPLEMENTED(); - } }; // static
diff --git a/chrome/browser/ui/tab_sharing/tab_sharing_infobar_delegate_unittest.cc b/chrome/browser/ui/tab_sharing/tab_sharing_infobar_delegate_unittest.cc index 0656057..0596b924 100644 --- a/chrome/browser/ui/tab_sharing/tab_sharing_infobar_delegate_unittest.cc +++ b/chrome/browser/ui/tab_sharing/tab_sharing_infobar_delegate_unittest.cc
@@ -32,8 +32,6 @@ content::MediaStreamUI::SourceCallback source_callback) override { return 0; } - - void SetStopCallback(base::OnceClosure stop_callback) override {} }; } // namespace
diff --git a/chrome/browser/ui/views/screen_capture_notification_ui_views.cc b/chrome/browser/ui/views/screen_capture_notification_ui_views.cc index 71c465c..1fb4059 100644 --- a/chrome/browser/ui/views/screen_capture_notification_ui_views.cc +++ b/chrome/browser/ui/views/screen_capture_notification_ui_views.cc
@@ -82,8 +82,6 @@ base::OnceClosure stop_callback, content::MediaStreamUI::SourceCallback source_callback) override; - void SetStopCallback(base::OnceClosure stop_callback) override; - // views::WidgetDelegateView: void DeleteDelegate() override; views::ClientView* CreateClientView(views::Widget* widget) override; @@ -225,11 +223,6 @@ return 0; } -void ScreenCaptureNotificationUIViews::SetStopCallback( - base::OnceClosure stop_callback) { - stop_callback_ = std::move(stop_callback); -} - void ScreenCaptureNotificationUIViews::DeleteDelegate() { NotifyStopped(); }
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc index 13cbfef..5c8c20e 100644 --- a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc +++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc
@@ -152,10 +152,6 @@ return 0; } -void TabSharingUIViews::SetStopCallback(base::OnceClosure stop_callback) { - stop_callback_ = std::move(stop_callback); -} - void TabSharingUIViews::StartSharing(infobars::InfoBar* infobar) { if (source_callback_.is_null()) return;
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h index de19b84..62e5a4e 100644 --- a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h +++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.h
@@ -45,8 +45,6 @@ base::OnceClosure stop_callback, content::MediaStreamUI::SourceCallback source_callback) override; - void SetStopCallback(base::OnceClosure stop_callback) override; - // TabSharingUI: // Runs |source_callback_| to start sharing the tab containing |infobar|. // Removes infobars on all tabs; OnStarted() will recreate the infobars with
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc index ace13f8..5c4b325 100644 --- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc +++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc
@@ -2,850 +2,63 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/base_paths.h" -#include "base/containers/flat_map.h" -#include "base/files/file_util.h" #include "base/path_service.h" -#include "base/strings/string_split.h" -#include "base/strings/string_util.h" -#include "base/test/bind.h" -#include "build/build_config.h" -#include "build/chromeos_buildflags.h" -#include "chrome/app/chrome_command_ids.h" -#include "chrome/browser/banners/test_app_banner_manager_desktop.h" -#include "chrome/browser/profiles/profile_manager.h" -#include "chrome/browser/ui/browser.h" -#include "chrome/browser/ui/browser_commands.h" -#include "chrome/browser/ui/browser_dialogs.h" -#include "chrome/browser/ui/browser_list.h" -#include "chrome/browser/ui/views/frame/browser_view.h" -#include "chrome/browser/ui/views/frame/toolbar_button_provider.h" -#include "chrome/browser/ui/views/page_action/page_action_icon_view.h" -#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h" -#include "chrome/browser/ui/web_applications/web_app_dialog_utils.h" -#include "chrome/browser/ui/web_applications/web_app_menu_model.h" -#include "chrome/browser/web_applications/components/app_registry_controller.h" -#include "chrome/browser/web_applications/components/install_finalizer.h" +#include "chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.h" #include "chrome/browser/web_applications/components/os_integration_manager.h" -#include "chrome/browser/web_applications/components/policy/web_app_policy_constants.h" -#include "chrome/browser/web_applications/components/policy/web_app_policy_manager.h" -#include "chrome/browser/web_applications/components/web_app_constants.h" -#include "chrome/browser/web_applications/components/web_app_id.h" -#include "chrome/browser/web_applications/components/web_app_provider_base.h" -#include "chrome/browser/web_applications/test/web_app_install_observer.h" -#include "chrome/browser/web_applications/web_app_provider.h" -#include "chrome/browser/web_applications/web_app_registrar.h" -#include "chrome/common/pref_names.h" #include "chrome/test/base/in_process_browser_test.h" -#include "chrome/test/base/ui_test_utils.h" -#include "components/prefs/scoped_user_pref_update.h" #include "content/public/test/browser_test.h" -#include "content/public/test/test_navigation_observer.h" -#include "extensions/browser/extension_dialog_auto_confirm.h" -#include "net/dns/mock_host_resolver.h" -#include "net/test/embedded_test_server/embedded_test_server.h" #include "services/network/public/cpp/network_switches.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h" -#include "third_party/re2/src/re2/re2.h" + +namespace web_app { namespace { -const std::string kTestCaseFilename = - "web_app_integration_browsertest_cases.csv"; -const std::string kExpectationsFilename = "TestExpectations"; -const std::string kPlatformName = -#if BUILDFLAG(IS_CHROMEOS_ASH) - "ChromeOS"; -#elif defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) - "Linux"; -#elif defined(OS_MAC) - "Mac"; -#elif defined(OS_WIN) - "Win"; -#endif // BUILDFLAG(IS_CHROMEOS_ASH) - -// Command-line switch that overrides test case input. Takes a comma -// separated list of testing actions. This aids in development of tests -// by allowing one to run a single test at a time, and avoid running every -// test case in the suite. -const char kWebAppIntegrationTestCase[] = "web-app-integration-test-case"; - -struct TabState { - TabState(GURL tab_url, bool is_tab_installable) - : url(tab_url), is_installable(is_tab_installable) {} - TabState& operator=(const TabState&) = default; - bool operator==(const TabState& other) const { - return url == other.url && is_installable == other.is_installable; - } - - GURL url; - bool is_installable; -}; - -struct BrowserState { - BrowserState(Browser* browser_ptr, - base::flat_map<content::WebContents*, TabState> tab_state, - content::WebContents* active_web_contents, - bool is_an_app_browser, - bool install_icon_visible, - bool launch_icon_visible) - : browser(browser_ptr), - tabs(std::move(tab_state)), - active_tab(active_web_contents), - is_app_browser(is_an_app_browser), - install_icon_shown(install_icon_visible), - launch_icon_shown(launch_icon_visible) {} - BrowserState& operator=(const BrowserState&) = default; - bool operator==(const BrowserState& other) const { - return browser == other.browser && tabs == other.tabs && - active_tab == other.active_tab && - is_app_browser == other.is_app_browser && - install_icon_shown == other.install_icon_shown && - launch_icon_shown == other.launch_icon_shown; - } - - Browser* browser; - base::flat_map<content::WebContents*, TabState> tabs; - content::WebContents* active_tab; - bool is_app_browser; - bool install_icon_shown; - bool launch_icon_shown; -}; - -struct AppState { - AppState(web_app::AppId app_id, - const std::string app_name, - const GURL app_scope, - const blink::mojom::DisplayMode& app_display_mode) - : id(app_id), - name(app_name), - scope(app_scope), - display_mode(app_display_mode) {} - AppState& operator=(const AppState&) = default; - bool operator==(const AppState& other) const { - return id == other.id && name == other.name && scope == other.scope && - display_mode == other.display_mode; - } - - web_app::AppId id; - std::string name; - GURL scope; - blink::mojom::DisplayMode display_mode; -}; - -struct StateSnapshot { - StateSnapshot(base::flat_map<Browser*, BrowserState> browser_state, - base::flat_map<web_app::AppId, AppState> app_state) - : browsers(std::move(browser_state)), apps(std::move(app_state)) {} - StateSnapshot& operator=(const StateSnapshot&) = default; - bool operator==(const StateSnapshot& other) const { - return browsers == other.browsers && apps == other.apps; - } - - base::flat_map<Browser*, BrowserState> browsers; - base::flat_map<web_app::AppId, AppState> apps; -}; - -base::Optional<BrowserState> GetStateForBrowser( - base::flat_map<Browser*, BrowserState> browser_state_map, - Browser* browser) { - auto it = browser_state_map.find(browser); - return it == browser_state_map.end() - ? base::nullopt - : base::make_optional<BrowserState>(it->second); -} - -base::Optional<TabState> GetStateForActiveTab(BrowserState browser_state) { - if (!browser_state.active_tab) { - return base::nullopt; - } - - auto it = browser_state.tabs.find(browser_state.active_tab); - DCHECK(it != browser_state.tabs.end()); - return base::make_optional<TabState>(it->second); -} - -base::Optional<AppState> GetStateForAppId( - base::flat_map<web_app::AppId, AppState> apps, - web_app::AppId id) { - auto it = apps.find(id); - return it == apps.end() ? base::nullopt - : base::make_optional<AppState>(it->second); -} - -std::string StripAllWhitespace(std::string line) { - std::string output; - output.reserve(line.size()); - for (const char& c : line) { - if (!isspace(c)) { - output += c; - } - } - return output; -} - -bool IsInspectionAction(const std::string& action) { - return base::StartsWith(action, "assert_"); -} - // Returns the path of the requested file in the test data directory. -base::FilePath GetTestFilePath(const std::string& file_name) { +base::FilePath GetTestFileDir() { base::FilePath file_path; base::PathService::Get(base::DIR_SOURCE_ROOT, &file_path); file_path = file_path.Append(FILE_PATH_LITERAL("chrome")); file_path = file_path.Append(FILE_PATH_LITERAL("test")); file_path = file_path.Append(FILE_PATH_LITERAL("data")); - file_path = file_path.Append(FILE_PATH_LITERAL("web_apps")); - return file_path.AppendASCII(file_name); -} - -std::string GetCommandLineTestOverride() { - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - if (command_line->HasSwitch(kWebAppIntegrationTestCase)) { - return command_line->GetSwitchValueASCII(kWebAppIntegrationTestCase); - } - return ""; -} - -std::vector<std::string> ReadTestInputFile(const std::string& file_name) { - std::vector<std::string> test_cases; - std::string command_line_test_case = GetCommandLineTestOverride(); - if (!command_line_test_case.empty()) { - test_cases.push_back(StripAllWhitespace(command_line_test_case)); - return test_cases; - } - - base::FilePath file = GetTestFilePath(file_name); - std::string contents; - if (!base::ReadFileToString(file, &contents)) { - return test_cases; - } - - std::vector<std::string> file_lines = base::SplitString( - contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - for (const auto& line : file_lines) { - if (line[0] == '#') { - continue; - } - - if (line.find('|') == std::string::npos) { - test_cases.push_back(StripAllWhitespace(line)); - continue; - } - - std::vector<std::string> platforms_and_test = base::SplitString( - line, "|", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - if (platforms_and_test[0].find(kPlatformName) != std::string::npos) { - test_cases.push_back(StripAllWhitespace(platforms_and_test[1])); - } - } - - return test_cases; -} - -std::vector<std::string> GetPlatformIgnoredTests(const std::string& file_name) { - base::FilePath file = GetTestFilePath(file_name); - std::string contents; - std::vector<std::string> platform_expectations; - if (!base::ReadFileToString(file, &contents)) { - return platform_expectations; - } - - std::vector<std::string> file_lines = base::SplitString( - contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - for (const auto& line : file_lines) { - if (line[0] == '#') { - continue; - } - - std::string platform; - std::string expectation; - std::string test_case; - RE2::FullMatch( - line, "crbug.com/\\d* \\[ (\\w*) \\] \\[ (\\w*) \\] ([\\w*,\\s*]*)", - &platform, &expectation, &test_case); - if (platform == kPlatformName) { - if (expectation == "Skip") { - platform_expectations.push_back(StripAllWhitespace(test_case)); - } else { - NOTREACHED() << "Unsupported expectation " << expectation; - } - } - } - return platform_expectations; + return file_path.Append(FILE_PATH_LITERAL("web_apps")); } std::vector<std::string> BuildAllPlatformTestCaseSet() { - std::vector<std::string> test_cases_all = - ReadTestInputFile(kTestCaseFilename); - std::sort(test_cases_all.begin(), test_cases_all.end()); - - std::vector<std::string> ignored_cases = - GetPlatformIgnoredTests(kExpectationsFilename); - std::sort(ignored_cases.begin(), ignored_cases.end()); - - std::vector<std::string> final_tests(test_cases_all.size()); - auto iter = std::set_difference(test_cases_all.begin(), test_cases_all.end(), - ignored_cases.begin(), ignored_cases.end(), - final_tests.begin()); - final_tests.resize(iter - final_tests.begin()); - return final_tests; + return WebAppIntegrationBrowserTestBase::BuildAllPlatformTestCaseSet( + GetTestFileDir()); } } // anonymous namespace -namespace web_app { - -struct NavigateToSiteResult { - content::WebContents* web_contents; - webapps::TestAppBannerManagerDesktop* app_banner_manager; - bool installable; -}; - class WebAppIntegrationBrowserTest : public InProcessBrowserTest, public testing::WithParamInterface<std::string> { public: - WebAppIntegrationBrowserTest() - : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} - ~WebAppIntegrationBrowserTest() override = default; - - WebAppIntegrationBrowserTest(const WebAppIntegrationBrowserTest&) = delete; - WebAppIntegrationBrowserTest& operator=(const WebAppIntegrationBrowserTest&) = - delete; + WebAppIntegrationBrowserTest() : helper_(this) {} // InProcessBrowserTest void SetUp() override { - https_server_.AddDefaultHandlers(GetChromeTestDataDir()); - ASSERT_TRUE(https_server_.Start()); - - webapps::TestAppBannerManagerDesktop::SetUp(); - + helper_.SetUp(GetChromeTestDataDir()); InProcessBrowserTest::SetUp(); } // BrowserTestBase - void SetUpOnMainThread() override { - os_hooks_suppress_ = - OsIntegrationManager::ScopedSuppressOsHooksForTesting(); - pwa_install_view_ = - BrowserView::GetBrowserViewForBrowser(browser()) - ->toolbar_button_provider() - ->GetPageActionIconView(PageActionIconType::kPwaInstall); - ASSERT_TRUE(pwa_install_view_); - EXPECT_FALSE(pwa_install_view_->GetVisible()); - } + void SetUpOnMainThread() override { helper_.SetUpOnMainThread(); } void SetUpCommandLine(base::CommandLine* command_line) override { command_line->AppendSwitchASCII( network::switches::kUnsafelyTreatInsecureOriginAsSecure, - GetInstallableAppURL().GetOrigin().spec()); + helper_.GetInstallableAppURL().GetOrigin().spec()); } - // Test Framework - void ParseParams() { - std::string action_strings = GetParam(); - LOG(ERROR) << "Test case: " << action_strings; - testing_actions_ = base::SplitString( - action_strings, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - } - - // Non-assert actions implemented before assert actions. Implemented in - // alphabetical order. - void ExecuteAction(const std::string& action_string) { - if (base::EndsWith(action_string, "site_b")) { - FAIL() << "site_b actions not yet supported: " << action_string; - } - - if (!IsInspectionAction(action_string)) { - before_action_state_ = std::move(after_action_state_); - } - - if (base::StartsWith(action_string, "add_policy_app_internal_tabbed")) { - AddPolicyAppInternal(base::Value(kDefaultLaunchContainerTabValue)); - } else if (base::StartsWith(action_string, - "add_policy_app_internal_windowed")) { - AddPolicyAppInternal(base::Value(kDefaultLaunchContainerWindowValue)); - } else if (action_string == "close_pwa") { - ClosePWA(); - } else if (action_string == "install_create_shortcut_tabbed") { - InstallCreateShortcutTabbed(); - } else if (action_string == "install_omnibox_or_menu") { - InstallOmniboxOrMenu(); - } else if (base::StartsWith(action_string, "install_internal_windowed")) { - InstallOmniboxOrMenu(); - } else if (base::StartsWith(action_string, "launch_internal")) { - LaunchInternal(); - } else if (action_string == "list_apps_internal") { - ListAppsInternal(); - } else if (base::StartsWith(action_string, "navigate_browser_in_scope")) { - NavigateToSite(browser(), GetInScopeURL()); - } else if (base::StartsWith(action_string, "navigate_installable")) { - NavigateToSite(browser(), GetInstallableAppURL()); - } else if (action_string == "navigate_not_installable") { - NavigateToSite(browser(), GetNonInstallableAppURL()); - } else if (action_string == "remove_policy_app") { - RemovePolicyApp(); - } else if (base::StartsWith(action_string, "set_open_in_tab_internal")) { - SetOpenInTabInternal(); - } else if (base::StartsWith(action_string, "set_open_in_window_internal")) { - SetOpenInWindowInternal(); - } else if (action_string == "uninstall_from_menu") { - UninstallFromMenu(); - } else if (base::StartsWith(action_string, "uninstall_internal")) { - UninstallInternal(); - } else if (action_string == "assert_app_in_list_not_windowed") { - AssertAppInListNotWindowed(); - } else if (base::StartsWith(action_string, "assert_app_not_in_list")) { - AssertAppNotInList(); - } else if (action_string == "assert_display_mode_standalone_internal") { - AssertDisplayModeInternal(DisplayMode::kStandalone); - } else if (action_string == "assert_display_mode_browser_internal") { - AssertDisplayModeInternal(DisplayMode::kBrowser); - } else if (action_string == "assert_installable") { - AssertInstallable(); - } else if (action_string == "assert_install_icon_shown") { - AssertInstallIconShown(); - } else if (action_string == "assert_install_icon_not_shown") { - AssertInstallIconNotShown(); - } else if (action_string == "assert_launch_icon_shown") { - AssertLaunchIconShown(); - } else if (action_string == "assert_launch_icon_not_shown") { - AssertLaunchIconNotShown(); - } else if (action_string == "assert_no_crash") { - } else if (action_string == "assert_tab_created") { - AssertTabCreated(); - } else if (action_string == "assert_window_created") { - AssertWindowCreated(); - } else { - FAIL() << "Unimplemented action: " << action_string; - } - - if (IsInspectionAction(action_string)) { - DCHECK(!after_action_state_ || - *after_action_state_ == ConstructStateSnapshot()); - } else { - after_action_state_ = - std::make_unique<StateSnapshot>(ConstructStateSnapshot()); - } - } - - // Automated Testing Actions - void AddPolicyAppInternal(base::Value default_launch_container) { - GURL url = GetInstallableAppURL(); - auto* web_app_registrar = - WebAppProvider::Get(profile())->registrar().AsWebAppRegistrar(); - base::RunLoop run_loop; - WebAppInstallObserver observer(profile()); - observer.SetWebAppInstalledDelegate( - base::BindLambdaForTesting([&](const AppId& app_id) { - bool is_installed = web_app_registrar->IsInstalled(app_id); - GURL installed_url = web_app_registrar->GetAppStartUrl(app_id); - if (is_installed && installed_url.is_valid() && - installed_url.spec() == url.spec()) { - active_app_id_ = app_id; - run_loop.Quit(); - } - })); - { - base::Value item(base::Value::Type::DICTIONARY); - item.SetKey(kUrlKey, base::Value(url.spec())); - item.SetKey(kDefaultLaunchContainerKey, - std::move(default_launch_container)); - ListPrefUpdate update(profile()->GetPrefs(), - prefs::kWebAppInstallForceList); - update->Append(item.Clone()); - } - run_loop.Run(); - } - - void ClosePWA() { - DCHECK(app_browser_); - app_browser_->window()->Close(); - ui_test_utils::WaitForBrowserToClose(app_browser_); - } - - void InstallCreateShortcutTabbed() { - chrome::SetAutoAcceptWebAppDialogForTesting(/*auto_accept=*/true, - /*auto_open_in_window=*/false); - WebAppInstallObserver observer(profile()); - CHECK(chrome::ExecuteCommand(browser(), IDC_CREATE_SHORTCUT)); - active_app_id_ = observer.AwaitNextInstall(); - chrome::SetAutoAcceptWebAppDialogForTesting(false, false); - } - - web_app::AppId InstallOmniboxOrMenu() { - chrome::SetAutoAcceptPWAInstallConfirmationForTesting(true); - - web_app::AppId app_id; - base::RunLoop run_loop; - web_app::SetInstalledCallbackForTesting(base::BindLambdaForTesting( - [&app_id, &run_loop](const web_app::AppId& installed_app_id, - web_app::InstallResultCode code) { - app_id = installed_app_id; - run_loop.Quit(); - })); - - pwa_install_view()->ExecuteForTesting(); - - run_loop.Run(); - - chrome::SetAutoAcceptPWAInstallConfirmationForTesting(false); - active_app_id_ = app_id; - auto* browser_list = BrowserList::GetInstance(); - app_browser_ = browser_list->GetLastActive(); - DCHECK(AppBrowserController::IsWebApp(app_browser_)); - - return app_id; - } - - void LaunchInternal() { - auto* web_app_provider = GetProvider(); - AppRegistrar& app_registrar = web_app_provider->registrar(); - DisplayMode display_mode = - app_registrar.GetAppEffectiveDisplayMode(active_app_id_); - if (display_mode == blink::mojom::DisplayMode::kStandalone) { - app_browser_ = LaunchWebAppBrowserAndWait( - ProfileManager::GetActiveUserProfile(), active_app_id_); - } else { - ui_test_utils::UrlLoadObserver url_observer( - WebAppProviderBase::GetProviderBase(profile()) - ->registrar() - .GetAppLaunchUrl(active_app_id_), - content::NotificationService::AllSources()); - LaunchBrowserForWebAppInTab(profile(), active_app_id_); - url_observer.Wait(); - } - } - - void ListAppsInternal() { - auto* web_app_registrar = - WebAppProvider::Get(profile())->registrar().AsWebAppRegistrar(); - app_ids_ = web_app_registrar->GetAppIds(); - } - - NavigateToSiteResult NavigateToSite(Browser* browser, const GURL& url) { - content::WebContents* web_contents = GetCurrentTab(browser); - auto* app_banner_manager = - webapps::TestAppBannerManagerDesktop::FromWebContents(web_contents); - - ui_test_utils::NavigateToURL(browser, url); - bool installable = app_banner_manager->WaitForInstallableCheck(); - - last_navigation_result_ = - NavigateToSiteResult{web_contents, app_banner_manager, installable}; - site_installability_map_[url.spec()] = installable; - return last_navigation_result_; - } - - void RemovePolicyApp() { - GURL url = GetInstallableAppURL(); - base::RunLoop run_loop; - WebAppInstallObserver observer(profile()); - observer.SetWebAppUninstalledDelegate( - base::BindLambdaForTesting([&](const AppId& app_id) { - if (active_app_id_ == app_id) { - run_loop.Quit(); - } - })); - { - ListPrefUpdate update(profile()->GetPrefs(), - prefs::kWebAppInstallForceList); - update->EraseListValueIf([&](const base::Value& item) { - const base::Value* url_value = item.FindKey(kUrlKey); - return url_value && url_value->GetString() == url.spec(); - }); - } - run_loop.Run(); - } - - void SetOpenInTabInternal() { - auto& app_registry_controller = - WebAppProvider::Get(profile())->registry_controller(); - app_registry_controller.SetAppUserDisplayMode( - active_app_id_, blink::mojom::DisplayMode::kBrowser, true); - } - - void SetOpenInWindowInternal() { - auto& app_registry_controller = - WebAppProvider::Get(profile())->registry_controller(); - app_registry_controller.SetAppUserDisplayMode( - active_app_id_, blink::mojom::DisplayMode::kStandalone, true); - } - - // TODO(https://crbug.com/1159651): Support this action on CrOS. - void UninstallFromMenu() { - DCHECK(app_browser_); - base::RunLoop run_loop; - WebAppInstallObserver observer(profile()); - observer.SetWebAppUninstalledDelegate( - base::BindLambdaForTesting([&](const AppId& app_id) { - if (app_id == active_app_id_) { - run_loop.Quit(); - } - })); - - extensions::ScopedTestDialogAutoConfirm auto_confirm( - extensions::ScopedTestDialogAutoConfirm::ACCEPT); - auto app_menu_model = - std::make_unique<WebAppMenuModel>(/*provider=*/nullptr, app_browser_); - app_menu_model->Init(); - ui::MenuModel* model = app_menu_model.get(); - int index = -1; - const bool found = app_menu_model->GetModelAndIndexForCommandId( - WebAppMenuModel::kUninstallAppCommandId, &model, &index); - EXPECT_TRUE(found); - EXPECT_TRUE(model->IsEnabledAt(index)); - - app_menu_model->ExecuteCommand(WebAppMenuModel::kUninstallAppCommandId, - /*event_flags=*/0); - // The |app_menu_model| must be destroyed here, as the |run_loop| waits - // until the app is fully uninstalled, which includes closing and deleting - // the app_browser_. - app_menu_model.reset(); - app_browser_ = nullptr; - run_loop.Run(); - } - - void UninstallInternal() { - WebAppProviderBase* const provider = - WebAppProviderBase::GetProviderBase(profile()); - base::RunLoop run_loop; - - DCHECK(provider->install_finalizer().CanUserUninstallExternalApp( - active_app_id_)); - provider->install_finalizer().UninstallExternalAppByUser( - active_app_id_, base::BindLambdaForTesting([&](bool uninstalled) { - EXPECT_TRUE(uninstalled); - run_loop.Quit(); - })); - - run_loop.Run(); - } - - // Assert Actions - void AssertAppInListNotWindowed() { - EXPECT_TRUE(base::Contains(app_ids_, active_app_id_)); - DCHECK(after_action_state_); - base::Optional<AppState> app_state = - GetStateForAppId(after_action_state_->apps, active_app_id_); - ASSERT_TRUE(app_state.has_value()); - EXPECT_EQ(DisplayMode::kBrowser, app_state->display_mode); - } - - void AssertAppNotInList() { - EXPECT_FALSE(base::Contains(app_ids_, active_app_id_)); - } - - void AssertDisplayModeInternal(DisplayMode display_mode) { - DCHECK(after_action_state_); - base::Optional<AppState> app_state = - GetStateForAppId(after_action_state_->apps, active_app_id_); - ASSERT_TRUE(app_state.has_value()); - EXPECT_EQ(display_mode, app_state->display_mode); - } - - void AssertInstallable() { - DCHECK(after_action_state_); - base::Optional<BrowserState> browser_state = - GetStateForBrowser(after_action_state_->browsers, browser()); - ASSERT_TRUE(browser_state.has_value()); - base::Optional<TabState> active_tab = - GetStateForActiveTab(browser_state.value()); - ASSERT_TRUE(active_tab.has_value()); - EXPECT_TRUE(active_tab->is_installable); - } - - void AssertInstallIconShown() { - DCHECK(after_action_state_); - base::Optional<BrowserState> browser_state = - GetStateForBrowser(after_action_state_->browsers, browser()); - ASSERT_TRUE(browser_state.has_value()); - EXPECT_TRUE(browser_state->install_icon_shown); - EXPECT_TRUE(pwa_install_view()->GetVisible()); - } - - void AssertInstallIconNotShown() { - base::Optional<BrowserState> browser_state = - GetStateForBrowser(after_action_state_->browsers, browser()); - ASSERT_TRUE(browser_state.has_value()); - EXPECT_FALSE(browser_state->install_icon_shown); - EXPECT_FALSE(pwa_install_view()->GetVisible()); - } - - void AssertLaunchIconShown() { - DCHECK(after_action_state_); - base::Optional<BrowserState> browser_state = - GetStateForBrowser(after_action_state_->browsers, browser()); - ASSERT_TRUE(browser_state.has_value()); - EXPECT_TRUE(browser_state->launch_icon_shown); - } - - void AssertLaunchIconNotShown() { - DCHECK(after_action_state_); - base::Optional<BrowserState> browser_state = - GetStateForBrowser(after_action_state_->browsers, browser()); - ASSERT_TRUE(browser_state.has_value()); - EXPECT_FALSE(browser_state->launch_icon_shown); - } - - void AssertTabCreated() { - DCHECK(before_action_state_); - DCHECK(after_action_state_); - base::Optional<BrowserState> most_recent_browser_state = - GetStateForBrowser(after_action_state_->browsers, browser()); - base::Optional<BrowserState> previous_browser_state = - GetStateForBrowser(before_action_state_->browsers, browser()); - ASSERT_TRUE(most_recent_browser_state.has_value()); - ASSERT_TRUE(previous_browser_state.has_value()); - EXPECT_GT(most_recent_browser_state->tabs.size(), - previous_browser_state->tabs.size()); - - base::Optional<TabState> active_tab = - GetStateForActiveTab(most_recent_browser_state.value()); - ASSERT_TRUE(active_tab.has_value()); - EXPECT_EQ(GetInstallableAppURL(), active_tab->url); - } - - void AssertWindowCreated() { - DCHECK(before_action_state_); - DCHECK(after_action_state_); - EXPECT_GT(after_action_state_->browsers.size(), - before_action_state_->browsers.size()); - } - - GURL GetInstallableAppURL() { - return https_server_.GetURL("/banners/manifest_test_page.html"); - } - - GURL GetNonInstallableAppURL() { - return https_server_.GetURL("/banners/no_manifest_test_page.html"); - } - - GURL GetInScopeURL() { - return https_server_.GetURL("/banners/manifest_test_page.html"); - } - - GURL GetOutOfScopeURL() { - return https_server_.GetURL("/out_of_scope/index.html"); - } - - content::WebContents* GetCurrentTab(Browser* browser) { - return browser->tab_strip_model()->GetActiveWebContents(); - } - - Profile* profile() { return browser()->profile(); } - Browser* app_browser() { return app_browser_; } - WebAppProvider* GetProvider() { return WebAppProvider::Get(profile()); } - std::vector<std::string>& testing_actions() { return testing_actions_; } - PageActionIconView* pwa_install_view() { return pwa_install_view_; } - - private: - StateSnapshot ConstructStateSnapshot() { - base::flat_map<Browser*, BrowserState> browser_state; - auto* browser_list = BrowserList::GetInstance(); - for (Browser* browser : *browser_list) { - TabStripModel* tabs = browser->tab_strip_model(); - base::flat_map<content::WebContents*, TabState> tab_state_map; - for (int i = 0; i < tabs->count(); ++i) { - content::WebContents* tab = tabs->GetWebContentsAt(i); - DCHECK(tab); - GURL url = tab->GetURL(); - auto* app_banner_manager = - webapps::TestAppBannerManagerDesktop::FromWebContents(tab); - bool installable = app_banner_manager->WaitForInstallableCheck(); - - tab_state_map.emplace(tab, TabState(url, installable)); - } - bool is_app_browser = AppBrowserController::IsWebApp(browser); - bool install_icon_visible = false; - bool launch_icon_visible = false; - content::WebContents* active_tab = tabs->GetActiveWebContents(); - if (!is_app_browser) { - install_icon_visible = - GetAppMenuCommandState(IDC_INSTALL_PWA, browser) == kEnabled; - launch_icon_visible = - GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser) == kEnabled; - } - browser_state.emplace( - browser, BrowserState(browser, tab_state_map, active_tab, - AppBrowserController::IsWebApp(browser), - install_icon_visible, launch_icon_visible)); - } - - auto* registrar = WebAppProvider::Get(browser()->profile()) - ->registrar() - .AsWebAppRegistrar(); - auto app_ids = registrar->GetAppIds(); - base::flat_map<AppId, AppState> app_state; - for (const auto& app_id : app_ids) { - app_state.emplace( - app_id, AppState(app_id, registrar->GetAppShortName(app_id), - registrar->GetAppScope(app_id), - registrar->GetAppEffectiveDisplayMode(app_id))); - } - return StateSnapshot(std::move(browser_state), std::move(app_state)); - } - - std::unique_ptr<StateSnapshot> before_action_state_; - std::unique_ptr<StateSnapshot> after_action_state_; - base::flat_map<std::string, bool> site_installability_map_; - Browser* app_browser_ = nullptr; - std::vector<AppId> app_ids_; - std::vector<std::string> testing_actions_; - NavigateToSiteResult last_navigation_result_; - AppId active_app_id_; - net::EmbeddedTestServer https_server_; - PageActionIconView* pwa_install_view_ = nullptr; - ScopedOsHooksSuppress os_hooks_suppress_; + WebAppIntegrationBrowserTestBase helper_; }; -// Tests that installing a PWA will cause the install icon to be hidden, and -// the launch icon to be shown. -IN_PROC_BROWSER_TEST_F(WebAppIntegrationBrowserTest, - InstallAndVerifyUIUpdates) { - bool installable = - NavigateToSite(browser(), GetInstallableAppURL()).installable; - ASSERT_TRUE(installable); - - EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, browser()), kEnabled); - EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kEnabled); - EXPECT_TRUE(pwa_install_view()->GetVisible()); - EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser()), - kNotPresent); - - InstallOmniboxOrMenu(); - - chrome::NewTab(browser()); - NavigateToSite(browser(), GetInstallableAppURL()); - EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kNotPresent); - EXPECT_FALSE(pwa_install_view()->GetVisible()); - EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser()), - kEnabled); -} - -IN_PROC_BROWSER_TEST_F(WebAppIntegrationBrowserTest, LaunchInternal) { - auto* browser_list = BrowserList::GetInstance(); - EXPECT_EQ(1U, browser_list->size()); - EXPECT_FALSE(AppBrowserController::IsWebApp(browser_list->GetLastActive())); - NavigateToSite(browser(), GetInstallableAppURL()); - InstallOmniboxOrMenu(); - EXPECT_EQ(2U, browser_list->size()); - EXPECT_TRUE(AppBrowserController::IsWebApp(browser_list->GetLastActive())); - ClosePWA(); - EXPECT_EQ(1U, browser_list->size()); - EXPECT_FALSE(AppBrowserController::IsWebApp(browser_list->GetLastActive())); - LaunchInternal(); - EXPECT_EQ(2U, browser_list->size()); - EXPECT_TRUE(AppBrowserController::IsWebApp(browser_list->GetLastActive())); -} - IN_PROC_BROWSER_TEST_P(WebAppIntegrationBrowserTest, Default) { - ParseParams(); + helper_.ParseParams(GetParam()); - for (auto& action : testing_actions()) { - ExecuteAction(action); + for (auto& action : helper_.testing_actions()) { + helper_.ExecuteAction(action); } }
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc new file mode 100644 index 0000000..ce647e6 --- /dev/null +++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc
@@ -0,0 +1,747 @@ +// 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. + +#include "chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.h" + +#include "base/base_paths.h" +#include "base/containers/flat_map.h" +#include "base/files/file_util.h" +#include "base/strings/string_split.h" +#include "base/test/bind.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/browser_dialogs.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/views/frame/toolbar_button_provider.h" +#include "chrome/browser/ui/views/page_action/page_action_icon_view.h" +#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h" +#include "chrome/browser/ui/web_applications/web_app_dialog_utils.h" +#include "chrome/browser/ui/web_applications/web_app_menu_model.h" +#include "chrome/browser/web_applications/components/app_registry_controller.h" +#include "chrome/browser/web_applications/components/install_finalizer.h" +#include "chrome/browser/web_applications/components/policy/web_app_policy_constants.h" +#include "chrome/browser/web_applications/components/policy/web_app_policy_manager.h" +#include "chrome/browser/web_applications/components/web_app_constants.h" +#include "chrome/browser/web_applications/components/web_app_id.h" +#include "chrome/browser/web_applications/components/web_app_provider_base.h" +#include "chrome/browser/web_applications/test/web_app_install_observer.h" +#include "chrome/browser/web_applications/web_app_registrar.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "content/public/test/test_navigation_observer.h" +#include "extensions/browser/extension_dialog_auto_confirm.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h" +#include "third_party/re2/src/re2/re2.h" + +namespace web_app { + +namespace { + +const std::string kTestCaseFilename = + "web_app_integration_browsertest_cases.csv"; +const std::string kExpectationsFilename = "TestExpectations"; +const std::string kPlatformName = +#if BUILDFLAG(IS_CHROMEOS_ASH) + "ChromeOS"; +#elif defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) + "Linux"; +#elif defined(OS_MAC) + "Mac"; +#elif defined(OS_WIN) + "Win"; +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + +// Command-line switch that overrides test case input. Takes a comma +// separated list of testing actions. This aids in development of tests +// by allowing one to run a single test at a time, and avoid running every +// test case in the suite. +const char kWebAppIntegrationTestCase[] = "web-app-integration-test-case"; + +} // anonymous namespace + +BrowserState::BrowserState( + Browser* browser_ptr, + base::flat_map<content::WebContents*, TabState> tab_state, + content::WebContents* active_web_contents, + bool is_an_app_browser, + bool install_icon_visible, + bool launch_icon_visible) + : browser(browser_ptr), + tabs(std::move(tab_state)), + active_tab(active_web_contents), + is_app_browser(is_an_app_browser), + install_icon_shown(install_icon_visible), + launch_icon_shown(launch_icon_visible) {} +BrowserState::~BrowserState() = default; +BrowserState::BrowserState(const BrowserState&) = default; +bool BrowserState::operator==(const BrowserState& other) const { + return browser == other.browser && tabs == other.tabs && + active_tab == other.active_tab && + is_app_browser == other.is_app_browser && + install_icon_shown == other.install_icon_shown && + launch_icon_shown == other.launch_icon_shown; +} + +AppState::AppState(web_app::AppId app_id, + const std::string app_name, + const GURL app_scope, + const blink::mojom::DisplayMode& app_display_mode) + : id(app_id), + name(app_name), + scope(app_scope), + display_mode(app_display_mode) {} +AppState::~AppState() = default; +AppState::AppState(const AppState&) = default; +bool AppState::operator==(const AppState& other) const { + return id == other.id && name == other.name && scope == other.scope && + display_mode == other.display_mode; +} + +StateSnapshot::StateSnapshot( + base::flat_map<Browser*, BrowserState> browser_state, + base::flat_map<web_app::AppId, AppState> app_state) + : browsers(std::move(browser_state)), apps(std::move(app_state)) {} +StateSnapshot::~StateSnapshot() = default; +StateSnapshot::StateSnapshot(const StateSnapshot&) = default; +bool StateSnapshot::operator==(const StateSnapshot& other) const { + return browsers == other.browsers && apps == other.apps; +} + +WebAppIntegrationBrowserTestBase::WebAppIntegrationBrowserTestBase( + InProcessBrowserTest* in_process_browser_test) + : in_process_browser_test_(in_process_browser_test), + https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} + +WebAppIntegrationBrowserTestBase::~WebAppIntegrationBrowserTestBase() = default; + +// static +base::Optional<BrowserState> +WebAppIntegrationBrowserTestBase::GetStateForBrowser( + base::flat_map<Browser*, BrowserState> browser_state_map, + Browser* browser) { + auto it = browser_state_map.find(browser); + return it == browser_state_map.end() + ? base::nullopt + : base::make_optional<BrowserState>(it->second); +} + +// static +base::Optional<TabState> WebAppIntegrationBrowserTestBase::GetStateForActiveTab( + BrowserState browser_state) { + if (!browser_state.active_tab) { + return base::nullopt; + } + + auto it = browser_state.tabs.find(browser_state.active_tab); + DCHECK(it != browser_state.tabs.end()); + return base::make_optional<TabState>(it->second); +} + +// static +base::Optional<AppState> WebAppIntegrationBrowserTestBase::GetStateForAppId( + base::flat_map<web_app::AppId, AppState> apps, + web_app::AppId id) { + auto it = apps.find(id); + return it == apps.end() ? base::nullopt + : base::make_optional<AppState>(it->second); +} + +// static +bool WebAppIntegrationBrowserTestBase::IsInspectionAction( + const std::string& action) { + return base::StartsWith(action, "assert_"); +} + +// static +std::string WebAppIntegrationBrowserTestBase::StripAllWhitespace( + std::string line) { + std::string output; + output.reserve(line.size()); + for (const char& c : line) { + if (!isspace(c)) { + output += c; + } + } + return output; +} + +// static +std::string WebAppIntegrationBrowserTestBase::GetCommandLineTestOverride() { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(kWebAppIntegrationTestCase)) { + return command_line->GetSwitchValueASCII(kWebAppIntegrationTestCase); + } + return ""; +} + +void WebAppIntegrationBrowserTestBase::SetUp(base::FilePath test_data_dir) { + https_server_.AddDefaultHandlers(test_data_dir); + ASSERT_TRUE(https_server_.Start()); + + webapps::TestAppBannerManagerDesktop::SetUp(); +} + +void WebAppIntegrationBrowserTestBase::SetUpOnMainThread() { + os_hooks_suppress_ = OsIntegrationManager::ScopedSuppressOsHooksForTesting(); + + pwa_install_view_ = + BrowserView::GetBrowserViewForBrowser(in_process_browser_test_->browser()) + ->toolbar_button_provider() + ->GetPageActionIconView(PageActionIconType::kPwaInstall); + ASSERT_TRUE(pwa_install_view_); + EXPECT_FALSE(pwa_install_view_->GetVisible()); +} + +void WebAppIntegrationBrowserTestBase::ParseParams(std::string action_strings) { + // Useful for debugging since all tests are run in a single parameterized + // test. + LOG(ERROR) << "Test case: " << action_strings; + testing_actions_ = base::SplitString( + action_strings, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); +} + +base::FilePath WebAppIntegrationBrowserTestBase::GetTestFilePath( + base::FilePath test_data_dir, + const std::string& file_name) { + return test_data_dir.AppendASCII(file_name); +} + +std::vector<std::string> WebAppIntegrationBrowserTestBase::ReadTestInputFile( + base::FilePath test_data_dir, + const std::string& file_name) { + std::vector<std::string> test_cases; + std::string command_line_test_case = GetCommandLineTestOverride(); + if (!command_line_test_case.empty()) { + test_cases.push_back(StripAllWhitespace(command_line_test_case)); + return test_cases; + } + + base::FilePath file = GetTestFilePath(test_data_dir, file_name); + std::string contents; + if (!base::ReadFileToString(file, &contents)) { + return test_cases; + } + + std::vector<std::string> file_lines = base::SplitString( + contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + for (const auto& line : file_lines) { + if (line[0] == '#') { + continue; + } + + if (line.find('|') == std::string::npos) { + test_cases.push_back(StripAllWhitespace(line)); + continue; + } + + std::vector<std::string> platforms_and_test = base::SplitString( + line, "|", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + if (platforms_and_test[0].find(kPlatformName) != std::string::npos) { + test_cases.push_back(StripAllWhitespace(platforms_and_test[1])); + } + } + + return test_cases; +} + +std::vector<std::string> +WebAppIntegrationBrowserTestBase::GetPlatformIgnoredTests( + base::FilePath test_data_dir, + const std::string& file_name) { + base::FilePath file = GetTestFilePath(test_data_dir, file_name); + std::string contents; + std::vector<std::string> platform_expectations; + if (!base::ReadFileToString(file, &contents)) { + return platform_expectations; + } + + std::vector<std::string> file_lines = base::SplitString( + contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + for (const auto& line : file_lines) { + if (line[0] == '#') { + continue; + } + + std::string platform; + std::string expectation; + std::string test_case; + RE2::FullMatch( + line, "crbug.com/\\d* \\[ (\\w*) \\] \\[ (\\w*) \\] ([\\w*,\\s*]*)", + &platform, &expectation, &test_case); + if (platform == kPlatformName) { + if (expectation == "Skip") { + platform_expectations.push_back(StripAllWhitespace(test_case)); + } else { + NOTREACHED() << "Unsupported expectation " << expectation; + } + } + } + return platform_expectations; +} + +std::vector<std::string> +WebAppIntegrationBrowserTestBase::BuildAllPlatformTestCaseSet( + base::FilePath test_data_dir) { + std::vector<std::string> test_cases_all = + ReadTestInputFile(test_data_dir, kTestCaseFilename); + std::sort(test_cases_all.begin(), test_cases_all.end()); + + std::vector<std::string> ignored_cases = + GetPlatformIgnoredTests(test_data_dir, kExpectationsFilename); + std::sort(ignored_cases.begin(), ignored_cases.end()); + + std::vector<std::string> final_tests(test_cases_all.size()); + auto iter = std::set_difference(test_cases_all.begin(), test_cases_all.end(), + ignored_cases.begin(), ignored_cases.end(), + final_tests.begin()); + final_tests.resize(iter - final_tests.begin()); + return final_tests; +} + +// Non-assert actions implemented before assert actions. Implemented in +// alphabetical order. +void WebAppIntegrationBrowserTestBase::ExecuteAction( + const std::string& action_string) { + if (base::EndsWith(action_string, "site_b")) { + FAIL() << "site_b actions not yet supported: " << action_string; + } + + if (!IsInspectionAction(action_string)) { + before_action_state_ = std::move(after_action_state_); + } + + if (base::StartsWith(action_string, "add_policy_app_internal_tabbed")) { + AddPolicyAppInternal(base::Value(kDefaultLaunchContainerTabValue)); + } else if (base::StartsWith(action_string, + "add_policy_app_internal_windowed")) { + AddPolicyAppInternal(base::Value(kDefaultLaunchContainerWindowValue)); + } else if (action_string == "close_pwa") { + ClosePWA(); + } else if (action_string == "install_create_shortcut_tabbed") { + InstallCreateShortcutTabbed(); + } else if (base::StartsWith(action_string, "install_internal_windowed")) { + InstallOmniboxOrMenu(); + } else if (action_string == "install_omnibox_or_menu") { + InstallOmniboxOrMenu(); + } else if (base::StartsWith(action_string, "launch_internal")) { + LaunchInternal(); + } else if (action_string == "list_apps_internal") { + ListAppsInternal(); + } else if (base::StartsWith(action_string, "navigate_browser_in_scope")) { + NavigateToSite(browser(), GetInScopeURL()); + } else if (base::StartsWith(action_string, "navigate_installable")) { + NavigateToSite(browser(), GetInstallableAppURL()); + } else if (action_string == "navigate_not_installable") { + NavigateToSite(browser(), GetNonInstallableAppURL()); + } else if (action_string == "remove_policy_app") { + RemovePolicyApp(); + } else if (base::StartsWith(action_string, "set_open_in_tab_internal")) { + SetOpenInTabInternal(); + } else if (base::StartsWith(action_string, "set_open_in_window_internal")) { + SetOpenInWindowInternal(); + } else if (action_string == "uninstall_from_menu") { + UninstallFromMenu(); + } else if (base::StartsWith(action_string, "uninstall_internal")) { + UninstallInternal(); + } else if (action_string == "assert_app_in_list_not_windowed") { + AssertAppInListNotWindowed(); + } else if (base::StartsWith(action_string, "assert_app_not_in_list")) { + AssertAppNotInList(); + } else if (action_string == "assert_display_mode_standalone_internal") { + AssertDisplayModeInternal(DisplayMode::kStandalone); + } else if (action_string == "assert_display_mode_browser_internal") { + AssertDisplayModeInternal(DisplayMode::kBrowser); + } else if (action_string == "assert_installable") { + AssertInstallable(); + } else if (action_string == "assert_install_icon_shown") { + AssertInstallIconShown(); + } else if (action_string == "assert_install_icon_not_shown") { + AssertInstallIconNotShown(); + } else if (action_string == "assert_launch_icon_shown") { + AssertLaunchIconShown(); + } else if (action_string == "assert_launch_icon_not_shown") { + AssertLaunchIconNotShown(); + } else if (action_string == "assert_no_crash") { + } else if (action_string == "assert_tab_created") { + AssertTabCreated(); + } else if (action_string == "assert_window_created") { + AssertWindowCreated(); + } else { + FAIL() << "Unimplemented action: " << action_string; + } + + if (IsInspectionAction(action_string)) { + DCHECK(!after_action_state_ || + *after_action_state_ == ConstructStateSnapshot()); + } else { + after_action_state_ = + std::make_unique<StateSnapshot>(ConstructStateSnapshot()); + } +} + +// Automated Testing Actions +void WebAppIntegrationBrowserTestBase::AddPolicyAppInternal( + base::Value default_launch_container) { + GURL url = GetInstallableAppURL(); + auto* web_app_registrar = + WebAppProvider::Get(profile())->registrar().AsWebAppRegistrar(); + base::RunLoop run_loop; + WebAppInstallObserver observer(profile()); + observer.SetWebAppInstalledDelegate( + base::BindLambdaForTesting([&](const AppId& app_id) { + bool is_installed = web_app_registrar->IsInstalled(app_id); + GURL installed_url = web_app_registrar->GetAppStartUrl(app_id); + if (is_installed && installed_url.is_valid() && + installed_url.spec() == url.spec()) { + active_app_id_ = app_id; + run_loop.Quit(); + } + })); + { + base::Value item(base::Value::Type::DICTIONARY); + item.SetKey(kUrlKey, base::Value(url.spec())); + item.SetKey(kDefaultLaunchContainerKey, + std::move(default_launch_container)); + ListPrefUpdate update(profile()->GetPrefs(), + prefs::kWebAppInstallForceList); + update->Append(item.Clone()); + } + run_loop.Run(); +} + +void WebAppIntegrationBrowserTestBase::ClosePWA() { + DCHECK(app_browser_); + app_browser_->window()->Close(); + ui_test_utils::WaitForBrowserToClose(app_browser_); +} + +void WebAppIntegrationBrowserTestBase::InstallCreateShortcutTabbed() { + chrome::SetAutoAcceptWebAppDialogForTesting(/*auto_accept=*/true, + /*auto_open_in_window=*/false); + WebAppInstallObserver observer(profile()); + CHECK(chrome::ExecuteCommand(browser(), IDC_CREATE_SHORTCUT)); + active_app_id_ = observer.AwaitNextInstall(); + chrome::SetAutoAcceptWebAppDialogForTesting(false, false); +} + +web_app::AppId WebAppIntegrationBrowserTestBase::InstallOmniboxOrMenu() { + chrome::SetAutoAcceptPWAInstallConfirmationForTesting(true); + + web_app::AppId app_id; + base::RunLoop run_loop; + web_app::SetInstalledCallbackForTesting(base::BindLambdaForTesting( + [&app_id, &run_loop](const web_app::AppId& installed_app_id, + web_app::InstallResultCode code) { + app_id = installed_app_id; + run_loop.Quit(); + })); + + pwa_install_view()->ExecuteForTesting(); + + run_loop.Run(); + + chrome::SetAutoAcceptPWAInstallConfirmationForTesting(false); + active_app_id_ = app_id; + auto* browser_list = BrowserList::GetInstance(); + app_browser_ = browser_list->GetLastActive(); + DCHECK(AppBrowserController::IsWebApp(app_browser_)); + + return app_id; +} + +void WebAppIntegrationBrowserTestBase::LaunchInternal() { + auto* web_app_provider = GetProvider(); + AppRegistrar& app_registrar = web_app_provider->registrar(); + DisplayMode display_mode = + app_registrar.GetAppEffectiveDisplayMode(active_app_id_); + if (display_mode == blink::mojom::DisplayMode::kStandalone) { + app_browser_ = LaunchWebAppBrowserAndWait( + ProfileManager::GetActiveUserProfile(), active_app_id_); + } else { + ui_test_utils::UrlLoadObserver url_observer( + WebAppProviderBase::GetProviderBase(profile()) + ->registrar() + .GetAppLaunchUrl(active_app_id_), + content::NotificationService::AllSources()); + LaunchBrowserForWebAppInTab(profile(), active_app_id_); + url_observer.Wait(); + } +} + +void WebAppIntegrationBrowserTestBase::ListAppsInternal() { + auto* web_app_registrar = + WebAppProvider::Get(profile())->registrar().AsWebAppRegistrar(); + app_ids_ = web_app_registrar->GetAppIds(); +} + +NavigateToSiteResult WebAppIntegrationBrowserTestBase::NavigateToSite( + Browser* browser, + const GURL& url) { + content::WebContents* web_contents = GetCurrentTab(browser); + auto* app_banner_manager = + webapps::TestAppBannerManagerDesktop::FromWebContents(web_contents); + + ui_test_utils::NavigateToURL(browser, url); + bool installable = app_banner_manager->WaitForInstallableCheck(); + + last_navigation_result_ = + NavigateToSiteResult{web_contents, app_banner_manager, installable}; + site_installability_map_[url.spec()] = installable; + return last_navigation_result_; +} + +void WebAppIntegrationBrowserTestBase::RemovePolicyApp() { + GURL url = GetInstallableAppURL(); + base::RunLoop run_loop; + WebAppInstallObserver observer(profile()); + observer.SetWebAppUninstalledDelegate( + base::BindLambdaForTesting([&](const AppId& app_id) { + if (active_app_id_ == app_id) { + run_loop.Quit(); + } + })); + { + ListPrefUpdate update(profile()->GetPrefs(), + prefs::kWebAppInstallForceList); + update->EraseListValueIf([&](const base::Value& item) { + const base::Value* url_value = item.FindKey(kUrlKey); + return url_value && url_value->GetString() == url.spec(); + }); + } + run_loop.Run(); +} + +void WebAppIntegrationBrowserTestBase::SetOpenInTabInternal() { + auto& app_registry_controller = + WebAppProvider::Get(profile())->registry_controller(); + app_registry_controller.SetAppUserDisplayMode( + active_app_id_, blink::mojom::DisplayMode::kBrowser, true); +} + +void WebAppIntegrationBrowserTestBase::SetOpenInWindowInternal() { + auto& app_registry_controller = + WebAppProvider::Get(profile())->registry_controller(); + app_registry_controller.SetAppUserDisplayMode( + active_app_id_, blink::mojom::DisplayMode::kStandalone, true); +} + +// TODO(https://crbug.com/1159651): Support this action on CrOS. +void WebAppIntegrationBrowserTestBase::UninstallFromMenu() { + DCHECK(app_browser_); + base::RunLoop run_loop; + WebAppInstallObserver observer(profile()); + observer.SetWebAppUninstalledDelegate( + base::BindLambdaForTesting([&](const AppId& app_id) { + if (app_id == active_app_id_) { + run_loop.Quit(); + } + })); + + extensions::ScopedTestDialogAutoConfirm auto_confirm( + extensions::ScopedTestDialogAutoConfirm::ACCEPT); + auto app_menu_model = + std::make_unique<WebAppMenuModel>(/*provider=*/nullptr, app_browser_); + app_menu_model->Init(); + ui::MenuModel* model = app_menu_model.get(); + int index = -1; + const bool found = app_menu_model->GetModelAndIndexForCommandId( + WebAppMenuModel::kUninstallAppCommandId, &model, &index); + EXPECT_TRUE(found); + EXPECT_TRUE(model->IsEnabledAt(index)); + + app_menu_model->ExecuteCommand(WebAppMenuModel::kUninstallAppCommandId, + /*event_flags=*/0); + // The |app_menu_model| must be destroyed here, as the |run_loop| waits + // until the app is fully uninstalled, which includes closing and deleting + // the app_browser_. + app_menu_model.reset(); + app_browser_ = nullptr; + run_loop.Run(); +} + +void WebAppIntegrationBrowserTestBase::UninstallInternal() { + WebAppProviderBase* const provider = + WebAppProviderBase::GetProviderBase(profile()); + base::RunLoop run_loop; + + DCHECK(provider->install_finalizer().CanUserUninstallExternalApp( + active_app_id_)); + provider->install_finalizer().UninstallExternalAppByUser( + active_app_id_, base::BindLambdaForTesting([&](bool uninstalled) { + EXPECT_TRUE(uninstalled); + run_loop.Quit(); + })); + + run_loop.Run(); +} + +// Assert Actions +void WebAppIntegrationBrowserTestBase::AssertAppInListNotWindowed() { + EXPECT_TRUE(base::Contains(app_ids_, active_app_id_)); + DCHECK(after_action_state_); + base::Optional<AppState> app_state = + GetStateForAppId(after_action_state_->apps, active_app_id_); + ASSERT_TRUE(app_state.has_value()); + EXPECT_EQ(DisplayMode::kBrowser, app_state->display_mode); +} + +void WebAppIntegrationBrowserTestBase::AssertAppNotInList() { + EXPECT_FALSE(base::Contains(app_ids_, active_app_id_)); +} + +void WebAppIntegrationBrowserTestBase::AssertDisplayModeInternal( + DisplayMode display_mode) { + DCHECK(after_action_state_); + base::Optional<AppState> app_state = + GetStateForAppId(after_action_state_->apps, active_app_id_); + ASSERT_TRUE(app_state.has_value()); + EXPECT_EQ(display_mode, app_state->display_mode); +} + +void WebAppIntegrationBrowserTestBase::AssertInstallable() { + DCHECK(after_action_state_); + base::Optional<BrowserState> browser_state = + GetStateForBrowser(after_action_state_->browsers, browser()); + ASSERT_TRUE(browser_state.has_value()); + base::Optional<TabState> active_tab = + GetStateForActiveTab(browser_state.value()); + ASSERT_TRUE(active_tab.has_value()); + EXPECT_TRUE(active_tab->is_installable); +} + +void WebAppIntegrationBrowserTestBase::AssertInstallIconShown() { + DCHECK(after_action_state_); + base::Optional<BrowserState> browser_state = + GetStateForBrowser(after_action_state_->browsers, browser()); + ASSERT_TRUE(browser_state.has_value()); + EXPECT_TRUE(browser_state->install_icon_shown); + EXPECT_TRUE(pwa_install_view()->GetVisible()); +} + +void WebAppIntegrationBrowserTestBase::AssertInstallIconNotShown() { + base::Optional<BrowserState> browser_state = + GetStateForBrowser(after_action_state_->browsers, browser()); + ASSERT_TRUE(browser_state.has_value()); + EXPECT_FALSE(browser_state->install_icon_shown); + EXPECT_FALSE(pwa_install_view()->GetVisible()); +} + +void WebAppIntegrationBrowserTestBase::AssertLaunchIconShown() { + DCHECK(after_action_state_); + base::Optional<BrowserState> browser_state = + GetStateForBrowser(after_action_state_->browsers, browser()); + ASSERT_TRUE(browser_state.has_value()); + EXPECT_TRUE(browser_state->launch_icon_shown); +} + +void WebAppIntegrationBrowserTestBase::AssertLaunchIconNotShown() { + DCHECK(after_action_state_); + base::Optional<BrowserState> browser_state = + GetStateForBrowser(after_action_state_->browsers, browser()); + ASSERT_TRUE(browser_state.has_value()); + EXPECT_FALSE(browser_state->launch_icon_shown); +} + +void WebAppIntegrationBrowserTestBase::AssertTabCreated() { + DCHECK(before_action_state_); + DCHECK(after_action_state_); + base::Optional<BrowserState> most_recent_browser_state = + GetStateForBrowser(after_action_state_->browsers, browser()); + base::Optional<BrowserState> previous_browser_state = + GetStateForBrowser(before_action_state_->browsers, browser()); + ASSERT_TRUE(most_recent_browser_state.has_value()); + ASSERT_TRUE(previous_browser_state.has_value()); + EXPECT_GT(most_recent_browser_state->tabs.size(), + previous_browser_state->tabs.size()); + + base::Optional<TabState> active_tab = + GetStateForActiveTab(most_recent_browser_state.value()); + ASSERT_TRUE(active_tab.has_value()); + EXPECT_EQ(GetInstallableAppURL(), active_tab->url); +} + +void WebAppIntegrationBrowserTestBase::AssertWindowCreated() { + DCHECK(before_action_state_); + DCHECK(after_action_state_); + EXPECT_GT(after_action_state_->browsers.size(), + before_action_state_->browsers.size()); +} + +GURL WebAppIntegrationBrowserTestBase::GetInstallableAppURL() { + return https_server_.GetURL("/banners/manifest_test_page.html"); +} + +GURL WebAppIntegrationBrowserTestBase::GetNonInstallableAppURL() { + return https_server_.GetURL("/banners/no_manifest_test_page.html"); +} + +GURL WebAppIntegrationBrowserTestBase::GetInScopeURL() { + return https_server_.GetURL("/banners/manifest_test_page.html"); +} + +GURL WebAppIntegrationBrowserTestBase::GetOutOfScopeURL() { + return https_server_.GetURL("/out_of_scope/index.html"); +} + +content::WebContents* WebAppIntegrationBrowserTestBase::GetCurrentTab( + Browser* browser) { + return browser->tab_strip_model()->GetActiveWebContents(); +} + +StateSnapshot WebAppIntegrationBrowserTestBase::ConstructStateSnapshot() { + base::flat_map<Browser*, BrowserState> browser_state; + auto* browser_list = BrowserList::GetInstance(); + for (Browser* browser : *browser_list) { + TabStripModel* tabs = browser->tab_strip_model(); + base::flat_map<content::WebContents*, TabState> tab_state_map; + for (int i = 0; i < tabs->count(); ++i) { + content::WebContents* tab = tabs->GetWebContentsAt(i); + DCHECK(tab); + GURL url = tab->GetURL(); + auto* app_banner_manager = + webapps::TestAppBannerManagerDesktop::FromWebContents(tab); + bool installable = app_banner_manager->WaitForInstallableCheck(); + + tab_state_map.emplace(tab, TabState(url, installable)); + } + bool is_app_browser = AppBrowserController::IsWebApp(browser); + bool install_icon_visible = false; + bool launch_icon_visible = false; + content::WebContents* active_tab = tabs->GetActiveWebContents(); + if (!is_app_browser) { + install_icon_visible = + GetAppMenuCommandState(IDC_INSTALL_PWA, browser) == kEnabled; + launch_icon_visible = + GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser) == kEnabled; + } + browser_state.emplace( + browser, BrowserState(browser, tab_state_map, active_tab, + AppBrowserController::IsWebApp(browser), + install_icon_visible, launch_icon_visible)); + } + + auto* registrar = WebAppProvider::Get(browser()->profile()) + ->registrar() + .AsWebAppRegistrar(); + auto app_ids = registrar->GetAppIds(); + base::flat_map<AppId, AppState> app_state; + for (const auto& app_id : app_ids) { + app_state.emplace(app_id, + AppState(app_id, registrar->GetAppShortName(app_id), + registrar->GetAppScope(app_id), + registrar->GetAppEffectiveDisplayMode(app_id))); + } + return StateSnapshot(std::move(browser_state), std::move(app_state)); +} + +} // namespace web_app
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.h b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.h new file mode 100644 index 0000000..cd2201e --- /dev/null +++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.h
@@ -0,0 +1,177 @@ +// 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_UI_VIEWS_WEB_APPS_WEB_APP_INTEGRATION_BROWSERTEST_BASE_H_ +#define CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_INTEGRATION_BROWSERTEST_BASE_H_ + +#include "chrome/browser/banners/test_app_banner_manager_desktop.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/views/page_action/page_action_icon_view.h" +#include "chrome/browser/web_applications/components/os_integration_manager.h" +#include "chrome/browser/web_applications/components/web_app_id.h" +#include "chrome/browser/web_applications/web_app_provider.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "net/test/embedded_test_server/embedded_test_server.h" + +namespace web_app { + +struct TabState { + TabState(GURL tab_url, bool is_tab_installable) + : url(tab_url), is_installable(is_tab_installable) {} + TabState& operator=(const TabState&) = default; + bool operator==(const TabState& other) const { + return url == other.url && is_installable == other.is_installable; + } + + GURL url; + bool is_installable; +}; + +struct BrowserState { + BrowserState(Browser* browser_ptr, + base::flat_map<content::WebContents*, TabState> tab_state, + content::WebContents* active_web_contents, + bool is_an_app_browser, + bool install_icon_visible, + bool launch_icon_visible); + ~BrowserState(); + BrowserState(const BrowserState&); + bool operator==(const BrowserState& other) const; + + Browser* browser; + base::flat_map<content::WebContents*, TabState> tabs; + content::WebContents* active_tab; + bool is_app_browser; + bool install_icon_shown; + bool launch_icon_shown; +}; + +struct AppState { + AppState(web_app::AppId app_id, + const std::string app_name, + const GURL app_scope, + const blink::mojom::DisplayMode& app_display_mode); + ~AppState(); + AppState(const AppState&); + bool operator==(const AppState& other) const; + + web_app::AppId id; + std::string name; + GURL scope; + blink::mojom::DisplayMode display_mode; +}; + +struct StateSnapshot { + StateSnapshot(base::flat_map<Browser*, BrowserState> browser_state, + base::flat_map<web_app::AppId, AppState> app_state); + ~StateSnapshot(); + StateSnapshot(const StateSnapshot&); + bool operator==(const StateSnapshot& other) const; + + base::flat_map<Browser*, BrowserState> browsers; + base::flat_map<web_app::AppId, AppState> apps; +}; + +struct NavigateToSiteResult { + content::WebContents* web_contents; + webapps::TestAppBannerManagerDesktop* app_banner_manager; + bool installable; +}; + +class WebAppIntegrationBrowserTestBase { + public: + explicit WebAppIntegrationBrowserTestBase( + InProcessBrowserTest* in_process_browser_test); + ~WebAppIntegrationBrowserTestBase(); + + static base::Optional<BrowserState> GetStateForBrowser( + base::flat_map<Browser*, BrowserState> browser_state_map, + Browser* browser); + static base::Optional<TabState> GetStateForActiveTab( + BrowserState browser_state); + static base::Optional<AppState> GetStateForAppId( + base::flat_map<web_app::AppId, AppState> apps, + web_app::AppId id); + + static bool IsInspectionAction(const std::string& action); + static std::string StripAllWhitespace(std::string line); + static std::string GetCommandLineTestOverride(); + + void SetUp(base::FilePath test_data_dir); + void SetUpOnMainThread(); + + // Test Framework + static base::FilePath GetTestFilePath(base::FilePath test_data_dir, + const std::string& file_name); + static std::vector<std::string> ReadTestInputFile( + base::FilePath test_data_dir, + const std::string& file_name); + static std::vector<std::string> GetPlatformIgnoredTests( + base::FilePath test_data_dir, + const std::string& file_name); + static std::vector<std::string> BuildAllPlatformTestCaseSet( + base::FilePath test_data_dir); + void ParseParams(std::string action_strings); + void ExecuteAction(const std::string& action_string); + + // Automated Testing Actions + void AddPolicyAppInternal(base::Value default_launch_container); + void ClosePWA(); + void InstallCreateShortcutTabbed(); + web_app::AppId InstallOmniboxOrMenu(); + void LaunchInternal(); + void ListAppsInternal(); + NavigateToSiteResult NavigateToSite(Browser* browser, const GURL& url); + void RemovePolicyApp(); + void SetOpenInTabInternal(); + void SetOpenInWindowInternal(); + void UninstallFromMenu(); + void UninstallInternal(); + + // Assert Actions + void AssertAppInListNotWindowed(); + void AssertAppNotInList(); + void AssertDisplayModeInternal(DisplayMode display_mode); + void AssertInstallable(); + void AssertInstallIconShown(); + void AssertInstallIconNotShown(); + void AssertLaunchIconShown(); + void AssertLaunchIconNotShown(); + void AssertTabCreated(); + void AssertWindowCreated(); + + std::vector<std::string>& testing_actions() { return testing_actions_; } + GURL GetInstallableAppURL(); + + private: + StateSnapshot ConstructStateSnapshot(); + GURL GetNonInstallableAppURL(); + GURL GetInScopeURL(); + GURL GetOutOfScopeURL(); + + content::WebContents* GetCurrentTab(Browser* browser); + Browser* browser() { return in_process_browser_test_->browser(); } + Profile* profile() { return browser()->profile(); } + Browser* app_browser() { return app_browser_; } + WebAppProvider* GetProvider() { return WebAppProvider::Get(profile()); } + PageActionIconView* pwa_install_view() { return pwa_install_view_; } + + InProcessBrowserTest* in_process_browser_test_; + std::unique_ptr<StateSnapshot> before_action_state_; + std::unique_ptr<StateSnapshot> after_action_state_; + base::flat_map<std::string, bool> site_installability_map_; + Browser* app_browser_ = nullptr; + std::vector<AppId> app_ids_; + std::vector<std::string> testing_actions_; + NavigateToSiteResult last_navigation_result_; + AppId active_app_id_; + net::EmbeddedTestServer https_server_; + base::FilePath test_data_dir_; + PageActionIconView* pwa_install_view_ = nullptr; + ScopedOsHooksSuppress os_hooks_suppress_; +}; + +} // namespace web_app + +#endif // CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_INTEGRATION_BROWSERTEST_BASE_H_
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc index c0ed6e19..0f39a5e 100644 --- a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc +++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
@@ -712,7 +712,7 @@ DCHECK(profile); #if BUILDFLAG(IS_CHROMEOS_ASH) - if (chromeos::IsAccountManagerAvailable(profile)) { + if (ash::IsAccountManagerAvailable(profile)) { // Chrome OS Account Manager is enabled on this Profile and hence, all // account management flows will go through native UIs and not through a // tabbed browser window.
diff --git a/chrome/browser/ui/webui/settings/chromeos/apps_section.cc b/chrome/browser/ui/webui/settings/chromeos/apps_section.cc index eeb6db1..c9e20c5 100644 --- a/chrome/browser/ui/webui/settings/chromeos/apps_section.cc +++ b/chrome/browser/ui/webui/settings/chromeos/apps_section.cc
@@ -138,6 +138,19 @@ void AddGuestOsStrings(content::WebUIDataSource* html_source) { // These strings are used for both Crostini and Plugin VM. static constexpr webui::LocalizedString kLocalizedStrings[] = { + {"guestOsSharedPaths", IDS_SETTINGS_GUEST_OS_SHARED_PATHS}, + {"guestOsSharedPathsListHeading", + IDS_SETTINGS_GUEST_OS_SHARED_PATHS_LIST_HEADING}, + {"guestOsSharedPathsInstructionsRemove", + IDS_SETTINGS_GUEST_OS_SHARED_PATHS_INSTRUCTIONS_REMOVE}, + {"guestOsSharedPathsStopSharing", + IDS_SETTINGS_GUEST_OS_SHARED_PATHS_STOP_SHARING}, + {"guestOsSharedPathsRemoveFailureDialogTitle", + IDS_SETTINGS_GUEST_OS_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE}, + {"guestOsSharedPathsRemoveFailureTryAgain", + IDS_SETTINGS_GUEST_OS_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN}, + {"guestOsSharedPathsListEmptyMessage", + IDS_SETTINGS_GUEST_OS_SHARED_PATHS_LIST_EMPTY_MESSAGE}, {"guestOsSharedUsbDevicesLabel", IDS_SETTINGS_GUEST_OS_SHARED_USB_DEVICES_LABEL}, {"guestOsSharedUsbDevicesExtraDescription", @@ -265,12 +278,11 @@ IDS_SETTINGS_APP_DETAILS_TITLE, mojom::Subpage::kAppDetails, mojom::Subpage::kAppManagement, mojom::SearchResultIcon::kAppsGrid, mojom::SearchResultDefaultRank::kMedium, mojom::kAppDetailsSubpagePath); - generator->RegisterNestedSubpage(IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS, - mojom::Subpage::kPluginVmSharedPaths, - mojom::Subpage::kAppManagement, - mojom::SearchResultIcon::kAppsGrid, - mojom::SearchResultDefaultRank::kMedium, - mojom::kPluginVmSharedPathsSubpagePath); + generator->RegisterNestedSubpage( + IDS_SETTINGS_GUEST_OS_SHARED_PATHS, mojom::Subpage::kPluginVmSharedPaths, + mojom::Subpage::kAppManagement, mojom::SearchResultIcon::kAppsGrid, + mojom::SearchResultDefaultRank::kMedium, + mojom::kPluginVmSharedPathsSubpagePath); generator->RegisterNestedSubpage( IDS_SETTINGS_GUEST_OS_SHARED_USB_DEVICES_LABEL, mojom::Subpage::kPluginVmUsbPreferences, mojom::Subpage::kAppManagement, @@ -329,23 +341,10 @@ void AppsSection::AddPluginVmLoadTimeData( content::WebUIDataSource* html_source) { static constexpr webui::LocalizedString kLocalizedStrings[] = { - {"pluginVmSharedPaths", IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS}, - {"pluginVmSharedPathsListHeading", - IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_HEADING}, {"pluginVmSharedPathsInstructionsAdd", IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_ADD}, - {"pluginVmSharedPathsInstructionsRemove", - IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_REMOVE}, - {"pluginVmSharedPathsRemoveSharing", - IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING}, {"pluginVmSharedPathsRemoveFailureDialogMessage", IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_MESSAGE}, - {"pluginVmSharedPathsRemoveFailureDialogTitle", - IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE}, - {"pluginVmSharedPathsRemoveFailureTryAgain", - IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN}, - {"pluginVmSharedPathsListEmptyMessage", - IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_EMPTY_MESSAGE}, {"pluginVmSharedUsbDevicesDescription", IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_USB_DEVICES_DESCRIPTION}, {"pluginVmPermissionDialogCameraLabel",
diff --git a/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc b/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc index 7587911..c94e19d 100644 --- a/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc +++ b/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc
@@ -236,23 +236,10 @@ {"crostiniPageTitle", IDS_SETTINGS_CROSTINI_TITLE}, {"crostiniPageLabel", IDS_SETTINGS_CROSTINI_LABEL}, {"crostiniEnable", IDS_SETTINGS_TURN_ON}, - {"crostiniSharedPaths", IDS_SETTINGS_CROSTINI_SHARED_PATHS}, - {"crostiniSharedPathsListHeading", - IDS_SETTINGS_CROSTINI_SHARED_PATHS_LIST_HEADING}, {"crostiniSharedPathsInstructionsAdd", IDS_SETTINGS_CROSTINI_SHARED_PATHS_INSTRUCTIONS_ADD}, - {"crostiniSharedPathsInstructionsRemove", - IDS_SETTINGS_CROSTINI_SHARED_PATHS_INSTRUCTIONS_REMOVE}, - {"crostiniSharedPathsRemoveSharing", - IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_SHARING}, {"crostiniSharedPathsRemoveFailureDialogMessage", IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_DIALOG_MESSAGE}, - {"crostiniSharedPathsRemoveFailureDialogTitle", - IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE}, - {"crostiniSharedPathsRemoveFailureTryAgain", - IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN}, - {"crostiniSharedPathsListEmptyMessage", - IDS_SETTINGS_CROSTINI_SHARED_PATHS_LIST_EMPTY_MESSAGE}, {"crostiniExportImportTitle", IDS_SETTINGS_CROSTINI_EXPORT_IMPORT_TITLE}, {"crostiniExport", IDS_SETTINGS_CROSTINI_EXPORT}, {"crostiniExportLabel", IDS_SETTINGS_CROSTINI_EXPORT_LABEL}, @@ -473,7 +460,7 @@ // Manage shared folders. generator->RegisterNestedSubpage( - IDS_SETTINGS_CROSTINI_SHARED_PATHS, + IDS_SETTINGS_GUEST_OS_SHARED_PATHS, mojom::Subpage::kCrostiniManageSharedFolders, mojom::Subpage::kCrostiniDetails, mojom::SearchResultIcon::kPenguin, mojom::SearchResultDefaultRank::kMedium,
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler_unittest.cc index 474a14a..e82018c 100644 --- a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler_unittest.cc +++ b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler_unittest.cc
@@ -106,7 +106,7 @@ CHECK(base::CreateDirectory(my_files_path)); CHECK(storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( file_manager::util::GetDownloadsMountPointName(profile_), - storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), my_files_path)); } @@ -320,7 +320,7 @@ // Register android files mount point. CHECK(storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( file_manager::util::GetAndroidFilesMountPointName(), - storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), + storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), android_files_path)); // Add files in My files and android files.
diff --git a/chrome/browser/ui/webui/settings/chromeos/people_section.cc b/chrome/browser/ui/webui/settings/chromeos/people_section.cc index 2bf8f8fb..bcf9c1cd 100644 --- a/chrome/browser/ui/webui/settings/chromeos/people_section.cc +++ b/chrome/browser/ui/webui/settings/chromeos/people_section.cc
@@ -60,6 +60,8 @@ namespace settings { namespace { +using ::ash::IsAccountManagerAvailable; + const std::vector<SearchConcept>& GetPeopleSearchConcepts() { static const base::NoDestructor<std::vector<SearchConcept>> tags([] { std::vector<SearchConcept> all_tags({
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc index 988bb11..03f096b 100644 --- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc +++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1250,7 +1250,7 @@ #if BUILDFLAG(IS_CHROMEOS_ASH) // Toggles the Chrome OS Account Manager submenu in the People section. html_source->AddBoolean("isAccountManagerEnabled", - chromeos::IsAccountManagerAvailable(profile)); + ash::IsAccountManagerAvailable(profile)); #elif BUILDFLAG(IS_CHROMEOS_LACROS) html_source->AddBoolean("isAccountManagerEnabled", IsAccountManagerAvailable(profile));
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc index 8f51463..2f69077 100644 --- a/chrome/browser/ui/webui/settings/settings_ui.cc +++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -379,7 +379,7 @@ // TODO(jamescook): Sort out how account management is split between Chrome OS // and browser settings. - if (chromeos::IsAccountManagerAvailable(profile)) { + if (ash::IsAccountManagerAvailable(profile)) { chromeos::AccountManagerFactory* factory = g_browser_process->platform_part()->GetAccountManagerFactory(); chromeos::AccountManager* account_manager =
diff --git a/chrome/browser/ui/webui/signin/inline_login_dialog_chromeos.h b/chrome/browser/ui/webui/signin/inline_login_dialog_chromeos.h index cbaf956..ca1e1be 100644 --- a/chrome/browser/ui/webui/signin/inline_login_dialog_chromeos.h +++ b/chrome/browser/ui/webui/signin/inline_login_dialog_chromeos.h
@@ -17,6 +17,10 @@ class GURL; +namespace ash { +class AccountManagerUIImpl; +} + namespace chromeos { // Extends from |SystemWebDialogDelegate| to create an always-on-top but movable @@ -99,7 +103,7 @@ private: // `Show` method can be called directly only by `AccountManagerUIImpl` class. // To show the dialog, use `AccountManagerFacade`. - friend class AccountManagerUIImpl; + friend class ash::AccountManagerUIImpl; // Displays the dialog. |close_dialog_closure| will be called when the dialog // is closed.
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt index 7bf9bae..1f125d3 100644 --- a/chrome/build/linux.pgo.txt +++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@ -chrome-linux-master-1611856733-a21458dcae1f312e1b9bb9647f9f3b0865709090.profdata +chrome-linux-master-1611878402-7f3d0a17d68f247d75e69369a67930b8470b50fb.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt index be1da09..3fee1885 100644 --- a/chrome/build/mac.pgo.txt +++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@ -chrome-mac-master-1611856733-3e44d52638b87d5eb2c9ef1ab766a80a1b8db797.profdata +chrome-mac-master-1611878402-2d30cdfb2e2d99278586dac534fff3b65fc772ea.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index fccfcf8..2201b8bd 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-master-1611834165-4b335240b4b05daf7ebafae407d3b94205e08a4f.profdata +chrome-win32-master-1611867260-2a7afb31171a7c6ba4d158bc4269427e2f851b66.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 575f8950..6250839 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-master-1611834165-f4f84b17839308b62c7941fe22fb5fc879fe91da.profdata +chrome-win64-master-1611867260-5f4da81c3097621296b6304a437aecf17fe13b8e.profdata
diff --git a/chrome/common/extensions/api/_manifest_features.json b/chrome/common/extensions/api/_manifest_features.json index a2dbd61..d5e059f 100644 --- a/chrome/common/extensions/api/_manifest_features.json +++ b/chrome/common/extensions/api/_manifest_features.json
@@ -66,8 +66,7 @@ }, "commands": { "channel": "stable", - "extension_types": ["extension", "platform_app"], - "min_manifest_version": 2 + "extension_types": ["extension", "platform_app"] }, "content_scripts": { "channel": "stable", @@ -217,8 +216,7 @@ "legacy_packaged_app", "platform_app", "login_screen_extension" - ], - "min_manifest_version": 2 + ] }, "system_indicator": [ {
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json index 21cd3b6..c03de9e 100644 --- a/chrome/common/extensions/api/_permission_features.json +++ b/chrome/common/extensions/api/_permission_features.json
@@ -37,8 +37,7 @@ }, "activeTab": { "channel": "stable", - "extension_types": ["extension", "legacy_packaged_app"], - "min_manifest_version": 2 + "extension_types": ["extension", "legacy_packaged_app"] }, "activityLogPrivate": { "channel": "stable",
diff --git a/chrome/common/extensions/api/scripting.idl b/chrome/common/extensions/api/scripting.idl index 2060e8f4..ce1512ff 100644 --- a/chrome/common/extensions/api/scripting.idl +++ b/chrome/common/extensions/api/scripting.idl
@@ -70,7 +70,7 @@ dictionary InjectionResult { // The result of the script execution. - any result; + any? result; // The frame associated with the injection. long frameId;
diff --git a/chrome/common/extensions/manifest_unittest.cc b/chrome/common/extensions/manifest_unittest.cc index 4b23fea9..f43acfa 100644 --- a/chrome/common/extensions/manifest_unittest.cc +++ b/chrome/common/extensions/manifest_unittest.cc
@@ -19,6 +19,7 @@ #include "extensions/common/features/simple_feature.h" #include "extensions/common/install_warning.h" #include "extensions/common/manifest_constants.h" +#include "extensions/common/value_builder.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { @@ -182,50 +183,73 @@ MutateManifest(&manifest, keys::kLaunchWebURL, nullptr); } -// Verifies that the getters filter restricted keys. -TEST_F(ManifestUnitTest, RestrictedKeys) { - std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue()); - value->SetString(keys::kName, "extension"); - value->SetString(keys::kVersion, "1"); +// Verifies that the getters filter restricted keys taking into account the +// manifest version. +TEST_F(ManifestUnitTest, RestrictedKeys_ManifestVersion) { + std::unique_ptr<base::DictionaryValue> value = + DictionaryBuilder() + .Set(keys::kName, "extension") + .Set(keys::kVersion, "1") + .Set(keys::kManifestVersion, 2) + .Build(); - std::unique_ptr<Manifest> manifest( - new Manifest(Manifest::INTERNAL, std::move(value), - crx_file::id_util::GenerateId("extid"))); + auto manifest = + std::make_unique<Manifest>(Manifest::INTERNAL, std::move(value), + crx_file::id_util::GenerateId("extid")); std::string error; std::vector<InstallWarning> warnings; EXPECT_TRUE(manifest->ValidateManifest(&error, &warnings)); EXPECT_TRUE(error.empty()); EXPECT_TRUE(warnings.empty()); - // "Commands" requires manifest version 2. + // "host_permissions" requires manifest version 3. + MutateManifest(&manifest, keys::kHostPermissions, + std::make_unique<base::Value>(base::Value::Type::LIST)); const base::Value* output = nullptr; - MutateManifest(&manifest, keys::kCommands, - std::make_unique<base::DictionaryValue>()); - EXPECT_FALSE(manifest->HasKey(keys::kCommands)); - EXPECT_FALSE(manifest->Get(keys::kCommands, &output)); + EXPECT_FALSE(manifest->HasKey(keys::kHostPermissions)); + EXPECT_FALSE(manifest->Get(keys::kHostPermissions, &output)); + // Update the extension to be manifest_version: 3; the host_permissions + // should then be available. MutateManifest(&manifest, keys::kManifestVersion, - std::make_unique<base::Value>(2)); - EXPECT_TRUE(manifest->HasKey(keys::kCommands)); - EXPECT_TRUE(manifest->Get(keys::kCommands, &output)); + std::make_unique<base::Value>(3)); + EXPECT_TRUE(manifest->HasKey(keys::kHostPermissions)); + EXPECT_TRUE(manifest->Get(keys::kHostPermissions, &output)); +} - MutateManifest(&manifest, keys::kPageAction, - std::make_unique<base::DictionaryValue>()); +// Verifies that the getters filter restricted keys taking into account the +// item type. +TEST_F(ManifestUnitTest, RestrictedKeys_ItemType) { + std::unique_ptr<base::DictionaryValue> value = + DictionaryBuilder() + .Set(keys::kName, "item") + .Set(keys::kVersion, "1") + .Set(keys::kManifestVersion, 2) + .Set(keys::kPageAction, + std::make_unique<base::Value>(base::Value::Type::DICTIONARY)) + .Build(); + + auto manifest = + std::make_unique<Manifest>(Manifest::INTERNAL, std::move(value), + crx_file::id_util::GenerateId("extid")); + std::string error; + std::vector<InstallWarning> warnings; + EXPECT_TRUE(manifest->ValidateManifest(&error, &warnings)); + EXPECT_TRUE(error.empty()); + EXPECT_TRUE(warnings.empty()); AssertType(manifest.get(), Manifest::TYPE_EXTENSION); + + // Extensions can specify "page_action"... + const base::Value* output = nullptr; EXPECT_TRUE(manifest->HasKey(keys::kPageAction)); EXPECT_TRUE(manifest->Get(keys::kPageAction, &output)); - // Platform apps cannot have a "page_action" key. MutateManifest(&manifest, keys::kPlatformAppBackground, std::make_unique<base::DictionaryValue>()); AssertType(manifest.get(), Manifest::TYPE_PLATFORM_APP); + // ...But platform apps may not. EXPECT_FALSE(manifest->HasKey(keys::kPageAction)); EXPECT_FALSE(manifest->Get(keys::kPageAction, &output)); - MutateManifest(&manifest, keys::kPlatformAppBackground, nullptr); - - // Platform apps also can't have a "Commands" key. - EXPECT_FALSE(manifest->HasKey(keys::kCommands)); - EXPECT_FALSE(manifest->Get(keys::kCommands, &output)); } } // namespace extensions
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index a882060..1494bdd 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -1511,6 +1511,8 @@ "../browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc", "../browser/ui/views/web_apps/frame_toolbar/web_app_minimal_ui_test.cc", "../browser/ui/views/web_apps/web_app_integration_browsertest.cc", + "../browser/ui/views/web_apps/web_app_integration_browsertest_base.cc", + "../browser/ui/views/web_apps/web_app_integration_browsertest_base.h", "../browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc", "../browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc", "../browser/ui/views/webview_accessibility_browsertest.cc",
diff --git a/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js b/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js index 43e59a0..e428c53 100644 --- a/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js +++ b/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js
@@ -65,6 +65,79 @@ chrome.test.succeed(); }, + async function injectedFunctionReturnsNothing() { + const query = {url: 'http://example.com/*'}; + let tab = await getSingleTab(query); + const results = await new Promise(resolve => { + chrome.scripting.executeScript( + { + target: { + tabId: tab.id, + }, + // Note: This function has no return statement; in JS, this means + // the return value will be undefined. + function: () => { }, + }, + resolve); + }); + chrome.test.assertNoLastError(); + chrome.test.assertEq(1, results.length); + // NOTE: Undefined results are mapped to null in our bindings layer, + // because they converted from empty base::Values in the same way. + // NOTE AS WELL: We use `val === null` (rather than + // `assertEq(null, val)` because assertEq will classify null and undefined + // as equal. + chrome.test.assertTrue(results[0].result === null); + chrome.test.succeed(); + }, + + async function injectedFunctionReturnsNull() { + const query = {url: 'http://example.com/*'}; + let tab = await getSingleTab(query); + const results = await new Promise(resolve => { + chrome.scripting.executeScript( + { + target: { + tabId: tab.id, + }, + function: () => { return null; }, + }, + resolve); + }); + chrome.test.assertNoLastError(); + chrome.test.assertEq(1, results.length); + // NOTE: We use `val === null` (rather than `assertEq(null, val)` because + // assertEq will classify null and undefined as equal. + chrome.test.assertTrue(results[0].result === null); + chrome.test.succeed(); + }, + + async function injectedFunctionHasError() { + const query = {url: 'http://example.com/*'}; + let tab = await getSingleTab(query); + chrome.scripting.executeScript( + { + target: { + tabId: tab.id, + }, + // This will throw a runtime error, since foo, bar, and baz aren't + // defined. + function: () => { + foo.bar = baz; + return 3; + }, + }, + results => { + // TODO(devlin): Currently, we don't pass the error from the injected + // script back to the extension in any way. It'd be helpful to pass + // this along to the extension. + chrome.test.assertNoLastError(); + chrome.test.assertEq(1, results.length); + chrome.test.assertEq(null, results[0].result); + chrome.test.succeed(); + }); + }, + async function noSuchTab() { const nonExistentTabId = 99999; // NOTE(devlin): We can't use a fancy `await` here, because the lastError
diff --git a/chrome/test/data/webui/cr_components/customize_themes_test.js b/chrome/test/data/webui/cr_components/customize_themes_test.js index ff83ed3..6d79b5d 100644 --- a/chrome/test/data/webui/cr_components/customize_themes_test.js +++ b/chrome/test/data/webui/cr_components/customize_themes_test.js
@@ -234,6 +234,35 @@ }); }); + function assertSingleThemeIconIsFocusableWithTabKey(customizeThemesElement) { + const numberOfthemeIcons = + customizeThemesElement.shadowRoot.querySelectorAll('div cr-theme-icon') + .length; + const numberOfNoneFocusableThemeIcons = + customizeThemesElement.shadowRoot + .querySelectorAll('div[tabindex="-1"] cr-theme-icon') + .length; + assertEquals(numberOfNoneFocusableThemeIcons, numberOfthemeIcons - 1); + assertEquals( + customizeThemesElement.shadowRoot + .querySelectorAll('div[tabindex="0"] cr-theme-icon') + .length, + 1); + } + + test('No theme selected', () => { + const customizeThemesElement = createCustomizeThemesElement(); + // First item of the grid has tabindex 0. + const focusableIcons = customizeThemesElement.shadowRoot.querySelectorAll( + 'div[tabindex="0"] cr-theme-icon'); + assertEquals(focusableIcons.length, 1); + assertEquals( + focusableIcons[0], + customizeThemesElement.shadowRoot.querySelector( + 'div[id="autogeneratedThemeContainer"] cr-theme-icon')); + assertSingleThemeIconIsFocusableWithTabKey(customizeThemesElement); + }); + test('setting autogenerated theme selects and updates icon', async () => { // Arrange. const customizeThemesElement = createCustomizeThemesElement(); @@ -267,6 +296,11 @@ selectedIcons, customizeThemesElement.shadowRoot.querySelectorAll( 'div[aria-checked="true"] cr-theme-icon')); + assertDeepEquals( + selectedIcons, + customizeThemesElement.shadowRoot.querySelectorAll( + 'div[tabindex="0"] cr-theme-icon')); + assertSingleThemeIconIsFocusableWithTabKey(customizeThemesElement); }); test('setting default theme selects and updates icon', async () => { @@ -291,6 +325,11 @@ selectedIcons, customizeThemesElement.shadowRoot.querySelectorAll( 'div[aria-checked="true"] cr-theme-icon')); + assertDeepEquals( + selectedIcons, + customizeThemesElement.shadowRoot.querySelectorAll( + 'div[tabindex="0"] cr-theme-icon')); + assertSingleThemeIconIsFocusableWithTabKey(customizeThemesElement); }); test('setting Chrome theme selects and updates icon', async () => { @@ -328,6 +367,11 @@ selectedIcons, customizeThemesElement.shadowRoot.querySelectorAll( 'div[aria-checked="true"] cr-theme-icon')); + assertDeepEquals( + selectedIcons, + customizeThemesElement.shadowRoot.querySelectorAll( + 'div[tabindex="0"] cr-theme-icon')); + assertSingleThemeIconIsFocusableWithTabKey(customizeThemesElement); }); test('setting third-party theme shows uninstall UI', async () => {
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_page_test.js b/chrome/test/data/webui/settings/chromeos/crostini_page_test.js index b08c51dc..57fe36e 100644 --- a/chrome/test/data/webui/settings/chromeos/crostini_page_test.js +++ b/chrome/test/data/webui/settings/chromeos/crostini_page_test.js
@@ -153,7 +153,7 @@ subpage.$$('#crostini-shared-paths').click(); await test_util.flushTasks(); - subpage = crostiniPage.$$('settings-crostini-shared-paths'); + subpage = crostiniPage.$$('settings-guest-os-shared-paths'); assertTrue(!!subpage); }); @@ -973,12 +973,14 @@ }); }); + // Functionality is already tested in OSSettingsGuestOsSharedPathsTest, + // so just check that we correctly set up the page for our 'termina' VM. suite('SubPageSharedPaths', function() { let subpage; setup(async function() { setCrostiniPrefs( - true, {sharedPaths: {path1: ['termina'], path2: ['termina']}}); + true, {sharedPaths: {path1: ['termina'], path2: ['some-other-vm']}}); await test_util.flushTasks(); settings.Router.getInstance().navigateTo( @@ -986,75 +988,12 @@ await test_util.flushTasks(); Polymer.dom.flush(); - subpage = crostiniPage.$$('settings-crostini-shared-paths'); + subpage = crostiniPage.$$('settings-guest-os-shared-paths'); assertTrue(!!subpage); }); test('Basic', function() { - assertEquals( - 3, subpage.shadowRoot.querySelectorAll('.settings-box').length); - assertEquals(2, subpage.shadowRoot.querySelectorAll('.list-item').length); - }); - - test('Remove', async function() { - assertFalse(subpage.$.crostiniInstructionsRemove.hidden); - assertFalse(subpage.$.crostiniList.hidden); - assertTrue(subpage.$.crostiniListEmpty.hidden); - assertTrue(!!subpage.$$('.list-item cr-icon-button')); - const rows = '.list-item:not([hidden])'; - assertEquals(2, subpage.shadowRoot.querySelectorAll(rows).length); - - { - // Remove first shared path, still one left. - subpage.$$('.list-item cr-icon-button').click(); - const [vmName, path] = - await guestOsBrowserProxy.whenCalled('removeGuestOsSharedPath'); - assertEquals('termina', vmName); - assertEquals('path1', path); - setCrostiniPrefs(true, {sharedPaths: {path2: ['termina']}}); - } - - await test_util.flushTasks(); - Polymer.dom.flush(); - assertEquals(1, subpage.shadowRoot.querySelectorAll(rows).length); - assertFalse(subpage.$.crostiniInstructionsRemove.hidden); - - { - // Remove remaining shared path, none left. - guestOsBrowserProxy.resetResolver('removeGuestOsSharedPath'); - subpage.$$(`${rows} cr-icon-button`).click(); - const [vmName, path] = - await guestOsBrowserProxy.whenCalled('removeGuestOsSharedPath'); - assertEquals('termina', vmName); - assertEquals('path2', path); - setCrostiniPrefs(true, {sharedPaths: {}}); - } - - await test_util.flushTasks(); - Polymer.dom.flush(); - // Verify remove instructions are hidden, and empty list message is shown. - assertTrue(subpage.$.crostiniInstructionsRemove.hidden); - assertTrue(subpage.$.crostiniList.hidden); - assertFalse(subpage.$.crostiniListEmpty.hidden); - }); - - test('RemoveFailedRetry', async function() { - // Remove shared path fails. - guestOsBrowserProxy.removeSharedPathResult = false; - subpage.$$('.list-item cr-icon-button').click(); - - await guestOsBrowserProxy.whenCalled('removeGuestOsSharedPath'); - Polymer.dom.flush(); - assertTrue(subpage.$$('#removeSharedPathFailedDialog').open); - - // Click retry and make sure 'removeCrostiniSharedPath' is called - // and dialog is closed/removed. - guestOsBrowserProxy.removeSharedPathResult = true; - subpage.$$('#removeSharedPathFailedDialog') - .querySelector('.action-button') - .click(); - await guestOsBrowserProxy.whenCalled('removeGuestOsSharedPath'); - assertFalse(!!subpage.$$('#removeSharedPathFailedDialog')); + assertEquals(1, subpage.shadowRoot.querySelectorAll('.list-item').length); }); });
diff --git a/chrome/test/data/webui/settings/chromeos/app_management/plugin_vm_shared_paths_test.js b/chrome/test/data/webui/settings/chromeos/guest_os_shared_paths_test.js similarity index 86% rename from chrome/test/data/webui/settings/chromeos/app_management/plugin_vm_shared_paths_test.js rename to chrome/test/data/webui/settings/chromeos/guest_os_shared_paths_test.js index ddc8eb3..71f1da73 100644 --- a/chrome/test/data/webui/settings/chromeos/app_management/plugin_vm_shared_paths_test.js +++ b/chrome/test/data/webui/settings/chromeos/guest_os_shared_paths_test.js
@@ -26,7 +26,7 @@ } suite('SharedPaths', function() { - /** @type {?SettingsPluginVmSharedPathsElement} */ + /** @type {?SettingsGuestOsSharedPathsElement} */ let page = null; /** @type {?TestGuestOsBrowserProxy} */ @@ -49,7 +49,8 @@ guestOsBrowserProxy = new TestGuestOsBrowserProxy(); settings.GuestOsBrowserProxyImpl.instance_ = guestOsBrowserProxy; PolymerTest.clearBody(); - page = document.createElement('settings-plugin-vm-shared-paths'); + page = document.createElement('settings-guest-os-shared-paths'); + page.guestOsType = 'pluginVm'; document.body.appendChild(page); }); @@ -63,9 +64,9 @@ const rows = '.list-item:not([hidden])'; assertEquals(2, page.shadowRoot.querySelectorAll(rows).length); - assertFalse(page.$.pluginVmInstructionsRemove.hidden); - assertFalse(page.$.pluginVmList.hidden); - assertTrue(page.$.pluginVmListEmpty.hidden); + assertFalse(page.$.guestOsInstructionsRemove.hidden); + assertFalse(page.$.guestOsList.hidden); + assertTrue(page.$.guestOsListEmpty.hidden); assertTrue(!!page.$$('.list-item cr-icon-button')); // Remove first shared path, still one left. @@ -78,7 +79,7 @@ } await setPrefs({'path2': ['PvmDefault']}); assertEquals(1, page.shadowRoot.querySelectorAll(rows).length); - assertFalse(page.$.pluginVmInstructionsRemove.hidden); + assertFalse(page.$.guestOsInstructionsRemove.hidden); // Remove remaining shared path, none left. guestOsBrowserProxy.resetResolver('removeGuestOsSharedPath'); @@ -90,11 +91,11 @@ assertEquals('path2', path); } await setPrefs({'ignored': ['ignore']}); - assertTrue(page.$.pluginVmList.hidden); + assertTrue(page.$.guestOsList.hidden); // Verify remove instructions are hidden, and empty list message is shown. - assertTrue(page.$.pluginVmInstructionsRemove.hidden); - assertTrue(page.$.pluginVmList.hidden); - assertFalse(page.$.pluginVmListEmpty.hidden); + assertTrue(page.$.guestOsInstructionsRemove.hidden); + assertTrue(page.$.guestOsList.hidden); + assertFalse(page.$.guestOsListEmpty.hidden); }); test('RemoveFailedRetry', async function() {
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js index 01e7774f..4b001cd 100644 --- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js +++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -542,31 +542,6 @@ mocha.run(); }); -// Test fixture for the Plugin VM shared paths page. -// eslint-disable-next-line no-var -var OSSettingsAppManagementPluginVmSharedPathsTest = - class extends OSSettingsAppManagementBrowserTest { - /** @override */ - get browsePreload() { - return super.browsePreload + - 'app_management/plugin_vm_page/plugin_vm_shared_paths.html'; - } - - /** @override */ - get extraLibraries() { - return super.extraLibraries.concat([ - BROWSER_SETTINGS_PATH + '../test_browser_proxy.js', - 'app_management/plugin_vm_shared_paths_test.js', - ]); - } -}; - -TEST_F( - 'OSSettingsAppManagementPluginVmSharedPathsTest', 'MAYBE_AllJsTests', - () => { - mocha.run(); - }); - // Test fixture for the app management managed app view. // eslint-disable-next-line no-var var OSSettingsAppManagementManagedAppTest = @@ -717,6 +692,28 @@ mocha.grep('\\bSubPageSharedUsbDevices\\b').run(); }); +// Test fixture for the Guest OS shared paths page. +// eslint-disable-next-line no-var +var OSSettingsGuestOsSharedPathsTest = + class extends OSSettingsAppManagementBrowserTest { + /** @override */ + get browsePreload() { + return super.browsePreload + 'guest_os/guest_os_shared_paths.html'; + } + + /** @override */ + get extraLibraries() { + return super.extraLibraries.concat([ + BROWSER_SETTINGS_PATH + '../test_browser_proxy.js', + 'guest_os_shared_paths_test.js', + ]); + } +}; + +TEST_F('OSSettingsGuestOsSharedPathsTest', 'MAYBE_AllJsTests', () => { + mocha.run(); +}); + // Test fixture for the Guest OS shared USB devices page. // eslint-disable-next-line no-var var OSSettingsGuestOsSharedUsbDevicesTest =
diff --git a/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js b/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js index 9124562..ed13eb3 100644 --- a/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js +++ b/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js
@@ -8,10 +8,6 @@ super([ 'requestCrostiniInstallerView', 'requestRemoveCrostini', - 'getCrostiniSharedPathsDisplayText', - 'notifyCrostiniSharedUsbDevicesPageReady', - 'setCrostiniUsbDeviceShared', - 'removeCrostiniSharedPath', 'exportCrostiniContainer', 'importCrostiniContainer', 'requestCrostiniContainerUpgradeView', @@ -33,8 +29,6 @@ 'getCrostiniMicSharingEnabled', 'requestCrostiniInstallerStatus', ]); - this.sharedUsbDevices = []; - this.removeSharedPathResult = true; this.crostiniMicSharingEnabled = false; this.crostiniIsRunning = true; this.methodCalls_ = {}; @@ -77,30 +71,6 @@ this.methodCalled('requestRemoveCrostini'); } - /** override */ - getCrostiniSharedPathsDisplayText(paths) { - this.methodCalled('getCrostiniSharedPathsDisplayText'); - return Promise.resolve(paths.map(path => path + '-displayText')); - } - - /** @override */ - notifyCrostiniSharedUsbDevicesPageReady() { - this.methodCalled('notifyCrostiniSharedUsbDevicesPageReady'); - cr.webUIListenerCallback( - 'crostini-shared-usb-devices-changed', this.sharedUsbDevices); - } - - /** @override */ - setCrostiniUsbDeviceShared(guid, shared) { - this.methodCalled('setCrostiniUsbDeviceShared', [guid, shared]); - } - - /** override */ - removeCrostiniSharedPath(vmName, path) { - this.methodCalled('removeCrostiniSharedPath', [vmName, path]); - return Promise.resolve(this.removeSharedPathResult); - } - /** @override */ requestCrostiniInstallerStatus() { this.methodCalled('requestCrostiniInstallerStatus');
diff --git a/chrome/test/data/webui/signin/local_profile_customization_test.js b/chrome/test/data/webui/signin/local_profile_customization_test.js index 80e09ac..822fbc9 100644 --- a/chrome/test/data/webui/signin/local_profile_customization_test.js +++ b/chrome/test/data/webui/signin/local_profile_customization_test.js
@@ -27,9 +27,11 @@ document.body.innerHTML = ''; customizeProfileElement = /** @type {!LocalProfileCustomizationElement} */ ( document.createElement('local-profile-customization')); + customizeProfileElement.profileThemeInfo = browserProxy.profileThemeInfo; document.body.appendChild(customizeProfileElement); + await browserProxy.whenCalled('getProfileThemeInfo'); + browserProxy.resetResolver('getProfileThemeInfo'); await waitBeforeNextRender(customizeProfileElement); - await setProfileTheme(browserProxy.profileThemeInfo); } setup(function() {
diff --git a/chrome/test/media_router/BUILD.gn b/chrome/test/media_router/BUILD.gn index 1ebfd0a..0920c6e 100644 --- a/chrome/test/media_router/BUILD.gn +++ b/chrome/test/media_router/BUILD.gn
@@ -20,6 +20,7 @@ "media_router_integration_browsertest.cc", "media_router_integration_browsertest.h", "media_router_integration_ui_browsertest.cc", + "media_router_native_integration_browsertest.cc", "media_router_one_ua_integration_browsertest.cc", "media_router_ui_for_test.cc", "media_router_ui_for_test.h", @@ -30,6 +31,7 @@ "//chrome/app:generated_resources", "//chrome/browser", "//chrome/browser/media/router", + "//chrome/browser/media/router:test_support", "//chrome/browser/ui", "//chrome/common", "//chrome/test:test_support",
diff --git a/chrome/test/media_router/media_router_integration_browsertest.cc b/chrome/test/media_router/media_router_integration_browsertest.cc index ad7ef980..017e805b 100644 --- a/chrome/test/media_router/media_router_integration_browsertest.cc +++ b/chrome/test/media_router/media_router_integration_browsertest.cc
@@ -412,10 +412,6 @@ ASSERT_EQ(session_id, reconnected_session_id); } -IN_PROC_BROWSER_TEST_F(MediaRouterIntegrationBrowserTest, Basic) { - RunBasicTest(); -} - // Tests that creating a route with a local file opens the file in a new tab. // // This test is disabled because the test needs to wait until navigation is
diff --git a/chrome/test/media_router/media_router_native_integration_browsertest.cc b/chrome/test/media_router/media_router_native_integration_browsertest.cc new file mode 100644 index 0000000..2f04f51 --- /dev/null +++ b/chrome/test/media_router/media_router_native_integration_browsertest.cc
@@ -0,0 +1,52 @@ +// 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/media/router/mojo/media_router_desktop.h" +#include "chrome/browser/media/router/providers/test/test_media_route_provider.h" +#include "chrome/test/media_router/media_router_integration_browsertest.h" +#include "components/media_router/browser/media_router_factory.h" +#include "components/media_router/common/media_route_provider_helper.h" +#include "content/public/test/browser_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media_router { + +// TODO(crbug.com/1152593): Merge this class back into +// MediaRouterIntegrationBrowserTest once all the test cases have been converted +// to use the native test MRP instead of the extension MRP. Then the extension +// setup code in MediaRouterBaseBrowserTest can also be deleted. +class MediaRouterNativeIntegrationBrowserTest + : public MediaRouterIntegrationBrowserTest { + public: + MediaRouterNativeIntegrationBrowserTest() = default; + ~MediaRouterNativeIntegrationBrowserTest() override = default; + + void SetUpOnMainThread() override { + // We don't call super::SetUpOnMainThread() here so that we're not setting + // up the extension test MRP. + MediaRouterMojoImpl* router = static_cast<MediaRouterMojoImpl*>( + MediaRouterFactory::GetApiForBrowserContext(browser()->profile())); + mojo::PendingRemote<mojom::MediaRouter> media_router_remote; + mojo::PendingRemote<mojom::MediaRouteProvider> provider_remote; + router->BindToMojoReceiver( + media_router_remote.InitWithNewPipeAndPassReceiver()); + test_provider_ = std::make_unique<TestMediaRouteProvider>( + provider_remote.InitWithNewPipeAndPassReceiver(), + std::move(media_router_remote)); + router->RegisterMediaRouteProvider(MediaRouteProviderId::TEST, + std::move(provider_remote), + base::DoNothing()); + + test_ui_ = + MediaRouterUiForTest::GetOrCreateForWebContents(GetActiveWebContents()); + } + + std::unique_ptr<TestMediaRouteProvider> test_provider_; +}; + +IN_PROC_BROWSER_TEST_F(MediaRouterNativeIntegrationBrowserTest, Basic) { + RunBasicTest(); +} + +} // namespace media_router
diff --git a/chromecast/common/extensions_api/_manifest_features.json b/chromecast/common/extensions_api/_manifest_features.json index 079a4f5..c183c0a 100644 --- a/chromecast/common/extensions_api/_manifest_features.json +++ b/chromecast/common/extensions_api/_manifest_features.json
@@ -26,8 +26,7 @@ }, "commands": { "channel": "stable", - "extension_types": ["extension", "platform_app"], - "min_manifest_version": 2 + "extension_types": ["extension", "platform_app"] }, "options_page": { "channel": "stable",
diff --git a/chromeos/components/account_manager/account_manager.h b/chromeos/components/account_manager/account_manager.h index ecd3464..36e06dba3 100644 --- a/chromeos/components/account_manager/account_manager.h +++ b/chromeos/components/account_manager/account_manager.h
@@ -421,4 +421,9 @@ } // namespace chromeos +// TODO(https://crbug.com/1164001): remove after moved to ash/. +namespace ash { +using ::chromeos::AccountManager; +} + #endif // CHROMEOS_COMPONENTS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_H_
diff --git a/chromeos/components/account_manager/account_manager_factory.h b/chromeos/components/account_manager/account_manager_factory.h index 2931137..4e7c17a 100644 --- a/chromeos/components/account_manager/account_manager_factory.h +++ b/chromeos/components/account_manager/account_manager_factory.h
@@ -64,4 +64,9 @@ } // namespace chromeos +// TODO(https://crbug.com/1164001): remove after moved to ash/. +namespace ash { +using ::chromeos::AccountManagerFactory; +} + #endif // CHROMEOS_COMPONENTS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_FACTORY_H_
diff --git a/chromeos/components/account_manager/account_manager_ui.h b/chromeos/components/account_manager/account_manager_ui.h index 2adb8f3..9b2952a 100644 --- a/chromeos/components/account_manager/account_manager_ui.h +++ b/chromeos/components/account_manager/account_manager_ui.h
@@ -36,4 +36,9 @@ } // namespace chromeos +// TODO(https://crbug.com/1164001): remove after moved to ash/ +namespace ash { +using ::chromeos::AccountManagerUI; +} + #endif // CHROMEOS_COMPONENTS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_UI_H_
diff --git a/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc b/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc index bed798d..d66c56af 100644 --- a/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc +++ b/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc
@@ -123,6 +123,9 @@ } bool ProximityAuthProfilePrefManager::IsEasyUnlockEnabled() const { + // Note: if GetFeatureState() is called in the first few hundred milliseconds + // of user session startup, it can incorrectly return a feature-default state + // of kProhibitedByPolicy. See https://crbug.com/1154766 for more. return multidevice_setup_client_->GetFeatureState( chromeos::multidevice_setup::mojom::Feature::kSmartLock) == chromeos::multidevice_setup::mojom::FeatureState::kEnabledByUser;
diff --git a/chromeos/components/proximity_auth/smart_lock_metrics_recorder.h b/chromeos/components/proximity_auth/smart_lock_metrics_recorder.h index 6d0e605..61e8baf 100644 --- a/chromeos/components/proximity_auth/smart_lock_metrics_recorder.h +++ b/chromeos/components/proximity_auth/smart_lock_metrics_recorder.h
@@ -77,6 +77,7 @@ kMaxValue = kPrimaryUserAbsent }; + // TODO(crbug.com/1171972): Deprecate the AuthMethodChoice metric. static void RecordSmartLockUnlockAuthMethodChoice( SmartLockAuthMethodChoice auth_method_choice); static void RecordSmartLockSignInAuthMethodChoice(
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc index 2d86e2a..0d59dc5 100644 --- a/chromeos/constants/chromeos_features.cc +++ b/chromeos/constants/chromeos_features.cc
@@ -305,6 +305,10 @@ const base::Feature kExoPointerLock{"ExoPointerLock", base::FEATURE_DISABLED_BY_DEFAULT}; +// Enable or disable bubble showing when an application gains any UI lock. +const base::Feature kExoLockNotification{"ExoLockNotification", + base::FEATURE_DISABLED_BY_DEFAULT}; + const base::Feature kDisablePeripheralDataAccessProtection{ "DisablePeripheralDataAccessProtection", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h index 5eb5e43..0d5ab4a 100644 --- a/chromeos/constants/chromeos_features.h +++ b/chromeos/constants/chromeos_features.h
@@ -148,6 +148,8 @@ COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kExoPointerLock; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) +extern const base::Feature kExoLockNotification; +COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kFamilyLinkOnSchoolDevice; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kFilesCameraFolder;
diff --git a/chromeos/constants/chromeos_switches.cc b/chromeos/constants/chromeos_switches.cc index bb2ff95..3034fce5 100644 --- a/chromeos/constants/chromeos_switches.cc +++ b/chromeos/constants/chromeos_switches.cc
@@ -66,10 +66,6 @@ // Signals the availability of the ARC instance on this device. const char kArcAvailable[] = "arc-available"; -// A JSON dictionary whose content is the same as cros config's -// /arc/build-properties. -const char kArcBuildProperties[] = "arc-build-properties"; - // Flag that forces ARC data be cleaned on each start. const char kArcDataCleanupOnStart[] = "arc-data-cleanup-on-start";
diff --git a/chromeos/constants/chromeos_switches.h b/chromeos/constants/chromeos_switches.h index 4a4f787c..7138a6d 100644 --- a/chromeos/constants/chromeos_switches.h +++ b/chromeos/constants/chromeos_switches.h
@@ -31,7 +31,6 @@ COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kAppOemManifestFile[]; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kArcAvailability[]; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kArcAvailable[]; -COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kArcBuildProperties[]; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kArcDataCleanupOnStart[]; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kArcDisableAppSync[]; COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
diff --git a/components/browser_ui/widget/android/BUILD.gn b/components/browser_ui/widget/android/BUILD.gn index df652e9..d4e68c2 100644 --- a/components/browser_ui/widget/android/BUILD.gn +++ b/components/browser_ui/widget/android/BUILD.gn
@@ -17,7 +17,6 @@ "java/src/org/chromium/components/browser_ui/widget/FadingEdgeScrollView.java", "java/src/org/chromium/components/browser_ui/widget/FadingShadow.java", "java/src/org/chromium/components/browser_ui/widget/FadingShadowView.java", - "java/src/org/chromium/components/browser_ui/widget/FeatureHighlightProvider.java", "java/src/org/chromium/components/browser_ui/widget/InsetObserverView.java", "java/src/org/chromium/components/browser_ui/widget/LoadingView.java", "java/src/org/chromium/components/browser_ui/widget/MaterialProgressBar.java",
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FeatureHighlightProvider.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FeatureHighlightProvider.java deleted file mode 100644 index f4d7672f..0000000 --- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/FeatureHighlightProvider.java +++ /dev/null
@@ -1,87 +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. - -package org.chromium.components.browser_ui.widget; - -import android.view.View; - -import androidx.annotation.ColorInt; -import androidx.annotation.IntDef; -import androidx.annotation.StringRes; -import androidx.annotation.StyleRes; -import androidx.appcompat.app.AppCompatActivity; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** A means of showing highlight for a particular feature as a form of in product help. */ -public class FeatureHighlightProvider { - /** - * These values determine text alignment and need to match the values in the closed-source - * library. - */ - @IntDef({TextAlignment.START, TextAlignment.CENTER, TextAlignment.END}) - @Retention(RetentionPolicy.SOURCE) - public @interface TextAlignment { - int START = 0; - int CENTER = 1; - int END = 2; - } - - /** The value for IPH timeout if the IPH will not timeout. */ - public static final long NO_TIMEOUT = -1; - - public FeatureHighlightProvider() {} - - /** - * Build and show a feature highlight bubble for a particular view. - * @param activity An activity to attach the highlight to. - * @param view The view to focus. - * @param headTextId The text shown in the header section of the bubble. - * @param headAlignment Alignment of the head text. - * @param headStyle Style of the head text size and color. - * @param bodyTextId The text shown in the body section of the bubble. - * @param bodyAlignment Alignment of the body text. - * @param bodyStyle Style of the body text size and color. - * @param pulseColor The inner color of the bubble. - * @param outerColor The outer color of the bubble. - * @param scrimColor The color of the out side of feature highlight. - * @param timeoutMs The amount of time in ms before the bubble disappears. - * @param tapToDismiss The feature highlight bubble can be dismissiable. - */ - public void buildForView(AppCompatActivity activity, View view, @StringRes int headTextId, - @TextAlignment int headAlignment, @StyleRes int headStyle, @StringRes int bodyTextId, - @TextAlignment int bodyAlignment, @StyleRes int bodyStyle, @ColorInt int pulseColor, - @ColorInt int outerColor, @ColorInt int scrimColor, long timeoutMs, - Boolean tapToDismiss) {} - - /** - * Build and show a feature highlight bubble for a particular view. - * @param activity An activity to attach the highlight to. - * @param view The view to focus. - * @param headTextId The text shown in the header section of the bubble. - * @param headAlignment Alignment of the head text. - * @param headStyle Style of the head text size and color. - * @param bodyTextId The text shown in the body section of the bubble. - * @param bodyAlignment Alignment of the body text. - * @param bodyStyle Style of the body text size and color. - * @param pulseColor The inner color of the bubble. - * @param outerColor The outer color of the bubble. - * @param scrimColor The color of the out side of feature highlight. - * @param timeoutMs The amount of time in ms before the bubble disappears. - * @param tapToDismiss The feature highlight bubble can be dismissiable. - * @param completeRunnable The Runnable to be called if the user tab on the view. - */ - public void buildForView(AppCompatActivity activity, View view, @StringRes int headTextId, - @TextAlignment int headAlignment, @StyleRes int headStyle, @StringRes int bodyTextId, - @TextAlignment int bodyAlignment, @StyleRes int bodyStyle, @ColorInt int pulseColor, - @ColorInt int outerColor, @ColorInt int scrimColor, long timeoutMs, - Boolean tapToDismiss, Runnable completeRunnable) {} - - /** - * Dismiss the feature highlight bubble for a particular view. - * @param activity An activity to attach the IPH to. - */ - public void dismiss(AppCompatActivity activity) {} -}
diff --git a/components/certificate_transparency/tools/make_ct_known_logs_list.py b/components/certificate_transparency/tools/make_ct_known_logs_list.py index f1563dbe..861f110 100755 --- a/components/certificate_transparency/tools/make_ct_known_logs_list.py +++ b/components/certificate_transparency/tools/make_ct_known_logs_list.py
@@ -83,13 +83,12 @@ def _is_log_disqualified(log): # Disqualified logs are denoted with state="retired" - assert (len(log.get("state").keys()) == 1) + assert (len(log.get("state")) == 1) log_state = list(log.get("state"))[0] return log_state == "retired" def _escape_c_string(s): - def _escape_char(c): if 32 <= ord(c) <= 126 and c not in '\\"': return c @@ -144,9 +143,8 @@ def _sorted_disqualified_logs(all_logs): - return sorted( - filter(_is_log_disqualified, all_logs), - key=lambda l: base64.b64decode(l["log_id"])) + return sorted([l for l in all_logs if _is_log_disqualified(l)], + key = lambda l: base64.b64decode(l["log_id"])) def _write_qualifying_logs_loginfo(f, qualifying_logs): @@ -157,7 +155,7 @@ def _is_log_once_or_currently_qualified(log): - assert (len(log.get("state").keys()) == 1) + assert (len(log.get("state")) == 1) return list(log.get("state"))[0] not in ("pending", "rejected") @@ -193,7 +191,7 @@ def main(): if len(sys.argv) != 3: - print("usage: %s in_loglist_json out_header" % sys.argv[0]) + print(("usage: %s in_loglist_json out_header" % sys.argv[0])) return 1 with open(sys.argv[1], "r") as infile, open(sys.argv[2], "w") as outfile: generate_cpp_file(infile, outfile)
diff --git a/components/media_router/browser/media_router_metrics.cc b/components/media_router/browser/media_router_metrics.cc index 7e749aaf..3ff662c 100644 --- a/components/media_router/browser/media_router_metrics.cc +++ b/components/media_router/browser/media_router_metrics.cc
@@ -38,8 +38,9 @@ return base_name + ".WiredDisplay"; case MediaRouteProviderId::ANDROID_CAF: return base_name + ".AndroidCaf"; - // |EXTENSION| and |UNKNOWN| use the base histogram name. + // The rest use the base histogram name. case MediaRouteProviderId::EXTENSION: + case MediaRouteProviderId::TEST: case MediaRouteProviderId::UNKNOWN: return base_name; }
diff --git a/components/media_router/common/media_route_provider_helper.cc b/components/media_router/common/media_route_provider_helper.cc index a48e9b9..b9d55c9 100644 --- a/components/media_router/common/media_route_provider_helper.cc +++ b/components/media_router/common/media_route_provider_helper.cc
@@ -12,6 +12,7 @@ constexpr const char kDial[] = "DIAL"; constexpr const char kCast[] = "CAST"; constexpr const char kAndroidCaf[] = "ANDROID_CAF"; +constexpr const char kTest[] = "TEST"; constexpr const char kUnknown[] = "UNKNOWN"; namespace media_router { @@ -28,6 +29,8 @@ return kDial; case ANDROID_CAF: return kAndroidCaf; + case TEST: + return kTest; case UNKNOWN: return kUnknown; } @@ -47,6 +50,8 @@ return MediaRouteProviderId::DIAL; } else if (provider_id == kAndroidCaf) { return MediaRouteProviderId::ANDROID_CAF; + } else if (provider_id == kTest) { + return MediaRouteProviderId::TEST; } else { return MediaRouteProviderId::UNKNOWN; }
diff --git a/components/media_router/common/media_route_provider_helper.h b/components/media_router/common/media_route_provider_helper.h index c61858b..7e9b667 100644 --- a/components/media_router/common/media_route_provider_helper.h +++ b/components/media_router/common/media_route_provider_helper.h
@@ -21,6 +21,7 @@ CAST, DIAL, ANDROID_CAF, + TEST, UNKNOWN // New values must be added above this value. };
diff --git a/components/media_router/common/mojom/media_router.mojom b/components/media_router/common/mojom/media_router.mojom index fa553f7b..ff162f39d 100644 --- a/components/media_router/common/mojom/media_router.mojom +++ b/components/media_router/common/mojom/media_router.mojom
@@ -297,6 +297,7 @@ CAST, DIAL, ANDROID_CAF, + TEST, }; // Creates a media route from |media_source| to the sink given by |sink_id|.
diff --git a/components/media_router/common/mojom/media_router_mojom_traits.h b/components/media_router/common/mojom/media_router_mojom_traits.h index 38eeb8d..80a77f4d 100644 --- a/components/media_router/common/mojom/media_router_mojom_traits.h +++ b/components/media_router/common/mojom/media_router_mojom_traits.h
@@ -513,6 +513,8 @@ return media_router::mojom::MediaRouteProvider_Id::DIAL; case media_router::MediaRouteProviderId::ANDROID_CAF: return media_router::mojom::MediaRouteProvider_Id::ANDROID_CAF; + case media_router::MediaRouteProviderId::TEST: + return media_router::mojom::MediaRouteProvider_Id::TEST; case media_router::MediaRouteProviderId::UNKNOWN: break; } @@ -539,6 +541,9 @@ case media_router::mojom::MediaRouteProvider_Id::ANDROID_CAF: *provider_id = media_router::MediaRouteProviderId::ANDROID_CAF; return true; + case media_router::mojom::MediaRouteProvider_Id::TEST: + *provider_id = media_router::MediaRouteProviderId::TEST; + return true; } return false; }
diff --git a/components/metrics/metrics_provider.h b/components/metrics/metrics_provider.h index 108cd7b8..28bc4fa 100644 --- a/components/metrics/metrics_provider.h +++ b/components/metrics/metrics_provider.h
@@ -34,6 +34,9 @@ virtual void AsyncInit(base::OnceClosure done_callback); // Called when a new MetricsLog is created. + // This can be used to log a histogram that will appear in the log. Not safe + // for some other uses, like user actions. + // TODO(crbug.com/1171830): Improve this. virtual void OnDidCreateMetricsLog(); // Called when metrics recording has been enabled.
diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc index 2855d94..5ba33668 100644 --- a/components/metrics/metrics_service.cc +++ b/components/metrics/metrics_service.cc
@@ -560,7 +560,11 @@ // Create the initial log. if (!initial_metrics_log_) { initial_metrics_log_ = CreateLog(MetricsLog::ONGOING_LOG); - delegating_provider_.OnDidCreateMetricsLog(); + // Note: We explicitly do not call OnDidCreateMetricsLog() here, as this + // function would have already been called in Start() and this log will + // already contain any histograms logged there. OnDidCreateMetricsLog() + // will be called again after the initial log is closed, for the next log. + // TODO(crbug.com/1171830): Consider getting rid of |initial_metrics_log_|. } rotation_scheduler_->InitTaskComplete(); @@ -782,6 +786,13 @@ log_manager_.FinishCurrentLog(log_store()); log_manager_.ResumePausedLog(); + // We call OnDidCreateMetricsLog() here for the next log. Normally, this is + // called when the log is created, but in this special case, the log we paused + // was created much earlier - by Start(). The histograms that were recorded + // via OnDidCreateMetricsLog() are now in the initial metrics log we just + // processed, so we need to record new ones for the next log. + delegating_provider_.OnDidCreateMetricsLog(); + // Store unsent logs, including the initial log that was just saved, so // that they're not lost in case of a crash before upload time. log_store()->TrimAndPersistUnsentLogs();
diff --git a/components/metrics/metrics_service.h b/components/metrics/metrics_service.h index 596c5f1..0dff15d 100644 --- a/components/metrics/metrics_service.h +++ b/components/metrics/metrics_service.h
@@ -203,7 +203,6 @@ PrefService* local_state, DelegatingProvider* delegating_provider); - private: // The MetricsService has a lifecycle that is stored as a state. // See metrics_service.cc for description of this lifecycle. enum State { @@ -213,6 +212,9 @@ SENDING_LOGS, // Sending logs an creating new ones when we run out. }; + State state() const { return state_; } + + private: enum ShutdownCleanliness { CLEANLY_SHUTDOWN = 0xdeadbeef, NEED_TO_SHUTDOWN = ~CLEANLY_SHUTDOWN
diff --git a/components/metrics/metrics_service_unittest.cc b/components/metrics/metrics_service_unittest.cc index 0adbd9d..b159eb1f 100644 --- a/components/metrics/metrics_service_unittest.cc +++ b/components/metrics/metrics_service_unittest.cc
@@ -13,6 +13,7 @@ #include "base/bind.h" #include "base/macros.h" #include "base/metrics/field_trial.h" +#include "base/metrics/histogram_functions.h" #include "base/metrics/metrics_hashes.h" #include "base/metrics/statistics_recorder.h" #include "base/metrics/user_metrics.h" @@ -77,7 +78,10 @@ : MetricsService(state_manager, client, local_state) {} ~TestMetricsService() override = default; + using MetricsService::INIT_TASK_SCHEDULED; using MetricsService::RecordCurrentEnvironmentHelper; + using MetricsService::SENDING_LOGS; + using MetricsService::state; // MetricsService: void SetPersistentSystemProfile(const std::string& serialized_proto, @@ -113,6 +117,18 @@ DISALLOW_COPY_AND_ASSIGN(TestMetricsLog); }; +const char kOnDidCreateMetricsLogHistogramName[] = "Test.OnDidCreateMetricsLog"; + +class TestMetricsProviderForOnDidCreateMetricsLog : public TestMetricsProvider { + public: + TestMetricsProviderForOnDidCreateMetricsLog() = default; + ~TestMetricsProviderForOnDidCreateMetricsLog() override = default; + + void OnDidCreateMetricsLog() override { + base::UmaHistogramBoolean(kOnDidCreateMetricsLogHistogramName, true); + } +}; + class MetricsServiceTest : public testing::Test { public: MetricsServiceTest() @@ -174,6 +190,33 @@ } } + // Returns the number of samples logged to the specified histogram or 0 if + // the histogram was not found. + int GetHistogramSampleCount(const ChromeUserMetricsExtension& uma_log, + base::StringPiece histogram_name) { + const auto histogram_name_hash = HashMetricName(histogram_name); + int samples = 0; + for (int i = 0; i < uma_log.histogram_event_size(); ++i) { + const auto& histogram = uma_log.histogram_event(i); + if (histogram.name_hash() == histogram_name_hash) { + for (int j = 0; j < histogram.bucket_size(); ++j) { + const auto& bucket = histogram.bucket(j); + // Per proto comments, count field not being set means 1 sample. + samples += (!bucket.has_count() ? 1 : bucket.count()); + } + } + } + return samples; + } + + // Returns the sampled count of the |kOnDidCreateMetricsLogHistogramName| + // histogram in the currently staged log in |test_log_store|. + int GetSampleCountOfOnDidCreateLogHistogram(MetricsLogStore* test_log_store) { + ChromeUserMetricsExtension log; + EXPECT_TRUE(DecodeLogDataToProto(test_log_store->staged_log(), &log)); + return GetHistogramSampleCount(log, kOnDidCreateMetricsLogHistogramName); + } + protected: scoped_refptr<base::TestSimpleTaskRunner> task_runner_; base::ThreadTaskRunnerHandle task_runner_handle_; @@ -369,6 +412,48 @@ EXPECT_EQ(1, uma_log.system_profile().stability().crash_count()); } +TEST_F(MetricsServiceTest, InitialLogsHaveOnDidCreateMetricsLogHistograms) { + EnableMetricsReporting(); + TestMetricsServiceClient client; + TestMetricsService service(GetMetricsStateManager(), &client, + GetLocalState()); + + // Create a provider that will log to |kOnDidCreateMetricsLogHistogramName| + // in OnDidCreateMetricsLog() + auto* test_provider = new TestMetricsProviderForOnDidCreateMetricsLog(); + service.RegisterMetricsProvider( + std::unique_ptr<MetricsProvider>(test_provider)); + + service.InitializeMetricsRecordingState(); + // Start() will create an initial log. + service.Start(); + ASSERT_EQ(TestMetricsService::INIT_TASK_SCHEDULED, service.state()); + + // Run pending tasks to finish the init task, which will create the + // |initial_metrics_log_|. + task_runner_->RunPendingTasks(); + ASSERT_EQ(TestMetricsService::SENDING_LOGS, service.state()); + + MetricsLogStore* test_log_store = service.LogStoreForTest(); + + // Stage the next log, which should be the |initial_metrics_log_|. + // Check that it has one sample in |kOnDidCreateMetricsLogHistogramName|. + test_log_store->StageNextLog(); + EXPECT_EQ(1, GetSampleCountOfOnDidCreateLogHistogram(test_log_store)); + + // Discard the staged log and close and stage the next one. This is the + // first "ongoing log". + // Check that it has one sample in |kOnDidCreateMetricsLogHistogramName|. + test_log_store->DiscardStagedLog(); + service.StageCurrentLogForTest(); + EXPECT_EQ(1, GetSampleCountOfOnDidCreateLogHistogram(test_log_store)); + + // Check one more log for good measure. + test_log_store->DiscardStagedLog(); + service.StageCurrentLogForTest(); + EXPECT_EQ(1, GetSampleCountOfOnDidCreateLogHistogram(test_log_store)); +} + TEST_F(MetricsServiceTest, MetricsProviderOnRecordingDisabledCalledOnInitialStop) { TestMetricsServiceClient client;
diff --git a/components/safe_browsing/content/password_protection/BUILD.gn b/components/safe_browsing/content/password_protection/BUILD.gn index d0baf03..af10f080 100644 --- a/components/safe_browsing/content/password_protection/BUILD.gn +++ b/components/safe_browsing/content/password_protection/BUILD.gn
@@ -16,6 +16,8 @@ "password_protection_request_content.h", "password_protection_service.cc", "password_protection_service.h", + "password_protection_service_base.cc", + "password_protection_service_base.h", "request_canceler_content.cc", "request_canceler_content.h", ]
diff --git a/components/safe_browsing/content/password_protection/password_protection_request_content.cc b/components/safe_browsing/content/password_protection/password_protection_request_content.cc index 863f80f2..c789183 100644 --- a/components/safe_browsing/content/password_protection/password_protection_request_content.cc +++ b/components/safe_browsing/content/password_protection/password_protection_request_content.cc
@@ -105,8 +105,10 @@ void PasswordProtectionRequestContent::MaybeLogPasswordReuseLookupEvent( RequestOutcome outcome, const LoginReputationClientResponse* response) { - password_protection_service()->MaybeLogPasswordReuseLookupEvent( - web_contents_, outcome, password_type(), response); + PasswordProtectionService* service = + static_cast<PasswordProtectionService*>(password_protection_service()); + service->MaybeLogPasswordReuseLookupEvent(web_contents_, outcome, + password_type(), response); } void PasswordProtectionRequestContent::MaybeAddPingToWebUI() { @@ -124,8 +126,9 @@ #if BUILDFLAG(SAFE_BROWSING_AVAILABLE) void PasswordProtectionRequestContent::GetDomFeatures() { content::RenderFrameHost* rfh = web_contents_->GetMainFrame(); - password_protection_service()->GetPhishingDetector(rfh->GetRemoteInterfaces(), - &phishing_detector_); + PasswordProtectionService* service = + static_cast<PasswordProtectionService*>(password_protection_service()); + service->GetPhishingDetector(rfh->GetRemoteInterfaces(), &phishing_detector_); dom_features_collection_complete_ = false; phishing_detector_->StartPhishingDetection( main_frame_url(), @@ -260,8 +263,10 @@ #if defined(OS_ANDROID) void PasswordProtectionRequestContent::SetReferringAppInfo() { + PasswordProtectionService* service = + static_cast<PasswordProtectionService*>(password_protection_service()); LoginReputationClientRequest::ReferringAppInfo referring_app_info = - password_protection_service()->GetReferringAppInfo(web_contents_); + service->GetReferringAppInfo(web_contents_); UMA_HISTOGRAM_ENUMERATION( "PasswordProtection.RequestReferringAppSource", referring_app_info.referring_app_source(),
diff --git a/components/safe_browsing/content/password_protection/password_protection_service.cc b/components/safe_browsing/content/password_protection/password_protection_service.cc index 140053e..d0ca1cb9 100644 --- a/components/safe_browsing/content/password_protection/password_protection_service.cc +++ b/components/safe_browsing/content/password_protection/password_protection_service.cc
@@ -9,23 +9,13 @@ #include <memory> #include <string> -#include "base/base64.h" -#include "base/bind.h" -#include "base/callback.h" #include "base/metrics/histogram_macros.h" -#include "base/stl_util.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_split.h" -#include "base/strings/string_util.h" -#include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/password_reuse_detector.h" #include "components/safe_browsing/content/password_protection/password_protection_navigation_throttle.h" -#include "components/safe_browsing/content/password_protection/password_protection_request.h" #include "components/safe_browsing/content/password_protection/password_protection_request_content.h" #include "components/safe_browsing/core/common/thread_utils.h" #include "components/safe_browsing/core/common/utils.h" -#include "components/safe_browsing/core/db/database_manager.h" #include "components/safe_browsing/core/features.h" #include "components/zoom/zoom_controller.h" #include "content/public/browser/navigation_handle.h" @@ -36,56 +26,12 @@ #include "third_party/blink/public/common/page/page_zoom.h" using content::WebContents; -using history::HistoryService; using password_manager::metrics_util::PasswordType; namespace safe_browsing { -using PasswordReuseEvent = LoginReputationClientRequest::PasswordReuseEvent; - -namespace { - -// Keys for storing password protection verdict into a DictionaryValue. -const int kRequestTimeoutMs = 10000; -const char kPasswordProtectionRequestUrl[] = - "https://sb-ssl.google.com/safebrowsing/clientreport/login"; - -} // namespace - -PasswordProtectionServiceBase::PasswordProtectionServiceBase( - const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager, - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, - HistoryService* history_service) - : database_manager_(database_manager), - url_loader_factory_(url_loader_factory) { - DCHECK(CurrentlyOnThread(ThreadID::UI)); - if (history_service) - history_service_observation_.Observe(history_service); - - common_spoofed_domains_ = {"login.live.com", "facebook.com", "box.com", - "google.com", "paypal.com", "apple.com", - "yahoo.com", "adobe.com", "amazon.com", - "linkedin.com"}; -} - -PasswordProtectionServiceBase::~PasswordProtectionServiceBase() { - tracker_.TryCancelAll(); - CancelPendingRequests(); - history_service_observation_.Reset(); - weak_factory_.InvalidateWeakPtrs(); -} - -// static -bool PasswordProtectionServiceBase::CanGetReputationOfURL(const GURL& url) { - if (!safe_browsing::CanGetReputationOfUrl(url)) { - return false; - } - const std::string hostname = url.HostNoBrackets(); - return !net::IsHostnameNonUnique(hostname); -} - #if defined(ON_FOCUS_PING_ENABLED) -void PasswordProtectionServiceBase::MaybeStartPasswordFieldOnFocusRequest( +void PasswordProtectionService::MaybeStartPasswordFieldOnFocusRequest( WebContents* web_contents, const GURL& main_frame_url, const GURL& password_form_action, @@ -112,7 +58,7 @@ } #endif -void PasswordProtectionServiceBase::MaybeStartProtectedPasswordEntryRequest( +void PasswordProtectionService::MaybeStartProtectedPasswordEntryRequest( WebContents* web_contents, const GURL& main_frame_url, const std::string& username, @@ -164,37 +110,7 @@ } } -bool PasswordProtectionServiceBase::ShouldShowModalWarning( - LoginReputationClientRequest::TriggerType trigger_type, - ReusedPasswordAccountType password_type, - LoginReputationClientResponse::VerdictType verdict_type) { - if (trigger_type != LoginReputationClientRequest::PASSWORD_REUSE_EVENT || - !IsSupportedPasswordTypeForModalWarning(password_type)) { - return false; - } - - return (verdict_type == LoginReputationClientResponse::PHISHING || - verdict_type == LoginReputationClientResponse::LOW_REPUTATION) && - IsWarningEnabled(password_type); -} - -LoginReputationClientResponse::VerdictType -PasswordProtectionServiceBase::GetCachedVerdict( - const GURL& url, - LoginReputationClientRequest::TriggerType trigger_type, - ReusedPasswordAccountType password_type, - LoginReputationClientResponse* out_response) { - return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; -} - -void PasswordProtectionServiceBase::CacheVerdict( - const GURL& url, - LoginReputationClientRequest::TriggerType trigger_type, - ReusedPasswordAccountType password_type, - const LoginReputationClientResponse& verdict, - const base::Time& receive_time) {} - -void PasswordProtectionServiceBase::StartRequest( +void PasswordProtectionService::StartRequest( WebContents* web_contents, const GURL& main_frame_url, const GURL& password_form_action, @@ -216,324 +132,6 @@ pending_requests_.insert(std::move(request)); } -bool PasswordProtectionServiceBase::CanSendPing( - LoginReputationClientRequest::TriggerType trigger_type, - const GURL& main_frame_url, - ReusedPasswordAccountType password_type) { - return IsPingingEnabled(trigger_type, password_type) && - !IsURLAllowlistedForPasswordEntry(main_frame_url) && - !IsInExcludedCountry(); -} - -void PasswordProtectionServiceBase::RequestFinished( - PasswordProtectionRequest* request, - RequestOutcome outcome, - std::unique_ptr<LoginReputationClientResponse> response) { - DCHECK(CurrentlyOnThread(ThreadID::UI)); - DCHECK(request); - - if (response) { - ReusedPasswordAccountType password_type = - GetPasswordProtectionReusedPasswordAccountType(request->password_type(), - request->username()); - if (outcome != RequestOutcome::RESPONSE_ALREADY_CACHED) { - CacheVerdict(request->main_frame_url(), request->trigger_type(), - password_type, *response, base::Time::Now()); - } - bool enable_warning_for_non_sync_users = base::FeatureList::IsEnabled( - safe_browsing::kPasswordProtectionForSignedInUsers); - if (!enable_warning_for_non_sync_users && - request->password_type() == PasswordType::OTHER_GAIA_PASSWORD) { - return; - } - - // If it's password alert mode and a Gsuite/enterprise account, we do not - // show a modal warning. - if (outcome == RequestOutcome::PASSWORD_ALERT_MODE && - (password_type.account_type() == ReusedPasswordAccountType::GSUITE || - password_type.account_type() == - ReusedPasswordAccountType::NON_GAIA_ENTERPRISE)) { - return; - } - - if (ShouldShowModalWarning(request->trigger_type(), password_type, - response->verdict_type())) { - username_for_last_shown_warning_ = request->username(); - reused_password_account_type_for_last_shown_warning_ = password_type; - saved_passwords_matching_domains_ = request->matching_domains(); - ShowModalWarning(request, response->verdict_type(), - response->verdict_token(), password_type); - request->set_is_modal_warning_showing(true); - } - } - - MaybeHandleDeferredNavigations(request); - - // If the request is canceled, the PasswordProtectionServiceBase is already - // partially destroyed, and we won't be able to log accurate metrics. - if (outcome != RequestOutcome::CANCELED) { - auto verdict = - response ? response->verdict_type() - : LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; - -// Disabled on Android, because enterprise reporting extension is not supported. -#if !defined(OS_ANDROID) - MaybeReportPasswordReuseDetected( - request, request->username(), request->password_type(), - verdict == LoginReputationClientResponse::PHISHING); -#endif - - // Persist a bit in CompromisedCredentials table when saved password is - // reused on a phishing or low reputation site. - auto is_unsafe_url = - verdict == LoginReputationClientResponse::PHISHING || - verdict == LoginReputationClientResponse::LOW_REPUTATION; - if (is_unsafe_url) { - PersistPhishedSavedPasswordCredential( - request->matching_reused_credentials()); - } - } - - // Remove request from |pending_requests_| list. If it triggers warning, add - // it into the !warning_reqeusts_| list. - for (auto it = pending_requests_.begin(); it != pending_requests_.end(); - it++) { - if (it->get() == request) { - if (request->is_modal_warning_showing()) - warning_requests_.insert(std::move(request)); - pending_requests_.erase(it); - break; - } - } -} - -void PasswordProtectionServiceBase::CancelPendingRequests() { - DCHECK(CurrentlyOnThread(ThreadID::UI)); - for (auto it = pending_requests_.begin(); it != pending_requests_.end();) { - PasswordProtectionRequest* request = it->get(); - // These are the requests for whom we're still waiting for verdicts. - // We need to advance the iterator before we cancel because canceling - // the request will invalidate it when RequestFinished is called. - it++; - request->Cancel(false); - } - DCHECK(pending_requests_.empty()); -} - -int PasswordProtectionServiceBase::GetStoredVerdictCount( - LoginReputationClientRequest::TriggerType trigger_type) { - return -1; -} - -scoped_refptr<SafeBrowsingDatabaseManager> -PasswordProtectionServiceBase::database_manager() { - return database_manager_; -} - -// static -GURL PasswordProtectionServiceBase::GetPasswordProtectionRequestUrl() { - GURL url(kPasswordProtectionRequestUrl); - std::string api_key = google_apis::GetAPIKey(); - DCHECK(!api_key.empty()); - return url.Resolve("?key=" + net::EscapeQueryParamValue(api_key, true)); -} - -// static -int PasswordProtectionServiceBase::GetRequestTimeoutInMS() { - return kRequestTimeoutMs; -} - -void PasswordProtectionServiceBase::FillUserPopulation( - LoginReputationClientRequest::TriggerType trigger_type, - LoginReputationClientRequest* request_proto) { - ChromeUserPopulation* user_population = request_proto->mutable_population(); - user_population->set_user_population( - IsEnhancedProtection() - ? ChromeUserPopulation::ENHANCED_PROTECTION - : IsExtendedReporting() ? ChromeUserPopulation::EXTENDED_REPORTING - : ChromeUserPopulation::SAFE_BROWSING); - user_population->set_profile_management_status( - GetProfileManagementStatus(GetBrowserPolicyConnector())); - user_population->set_is_history_sync_enabled(IsHistorySyncEnabled()); -#if BUILDFLAG(FULL_SAFE_BROWSING) - user_population->set_is_under_advanced_protection( - IsUnderAdvancedProtection()); -#endif - user_population->set_is_incognito(IsIncognito()); - user_population->set_is_mbb_enabled(IsUserMBBOptedIn()); -} - -void PasswordProtectionServiceBase::OnURLsDeleted( - history::HistoryService* history_service, - const history::DeletionInfo& deletion_info) { - GetTaskRunner(ThreadID::UI) - ->PostTask( - FROM_HERE, - base::BindRepeating(&PasswordProtectionServiceBase:: - RemoveUnhandledSyncPasswordReuseOnURLsDeleted, - GetWeakPtr(), deletion_info.IsAllHistory(), - deletion_info.deleted_rows())); -} - -void PasswordProtectionServiceBase::HistoryServiceBeingDeleted( - history::HistoryService* history_service) { - DCHECK(history_service_observation_.IsObservingSource(history_service)); - history_service_observation_.Reset(); -} - -bool PasswordProtectionServiceBase::IsWarningEnabled( - ReusedPasswordAccountType password_type) { - return GetPasswordProtectionWarningTriggerPref(password_type) == - PHISHING_REUSE; -} - -// static -ReusedPasswordType -PasswordProtectionServiceBase::GetPasswordProtectionReusedPasswordType( - password_manager::metrics_util::PasswordType password_type) { - switch (password_type) { - case PasswordType::SAVED_PASSWORD: - return PasswordReuseEvent::SAVED_PASSWORD; - case PasswordType::PRIMARY_ACCOUNT_PASSWORD: - return PasswordReuseEvent::SIGN_IN_PASSWORD; - case PasswordType::OTHER_GAIA_PASSWORD: - return PasswordReuseEvent::OTHER_GAIA_PASSWORD; - case PasswordType::ENTERPRISE_PASSWORD: - return PasswordReuseEvent::ENTERPRISE_PASSWORD; - case PasswordType::PASSWORD_TYPE_UNKNOWN: - return PasswordReuseEvent::REUSED_PASSWORD_TYPE_UNKNOWN; - case PasswordType::PASSWORD_TYPE_COUNT: - break; - } - NOTREACHED(); - return PasswordReuseEvent::REUSED_PASSWORD_TYPE_UNKNOWN; -} - -ReusedPasswordAccountType -PasswordProtectionServiceBase::GetPasswordProtectionReusedPasswordAccountType( - password_manager::metrics_util::PasswordType password_type, - const std::string& username) const { - ReusedPasswordAccountType reused_password_account_type; - switch (password_type) { - case PasswordType::SAVED_PASSWORD: - reused_password_account_type.set_account_type( - ReusedPasswordAccountType::SAVED_PASSWORD); - return reused_password_account_type; - case PasswordType::ENTERPRISE_PASSWORD: - reused_password_account_type.set_account_type( - ReusedPasswordAccountType::NON_GAIA_ENTERPRISE); - return reused_password_account_type; - case PasswordType::PRIMARY_ACCOUNT_PASSWORD: { - reused_password_account_type.set_is_account_syncing( - IsPrimaryAccountSyncing()); - if (!IsPrimaryAccountSignedIn()) { - reused_password_account_type.set_account_type( - ReusedPasswordAccountType::UNKNOWN); - return reused_password_account_type; - } - reused_password_account_type.set_account_type( - IsPrimaryAccountGmail() ? ReusedPasswordAccountType::GMAIL - : ReusedPasswordAccountType::GSUITE); - return reused_password_account_type; - } - case PasswordType::OTHER_GAIA_PASSWORD: { - AccountInfo account_info = GetSignedInNonSyncAccount(username); - if (account_info.account_id.empty()) { - reused_password_account_type.set_account_type( - ReusedPasswordAccountType::UNKNOWN); - return reused_password_account_type; - } - reused_password_account_type.set_account_type( - IsOtherGaiaAccountGmail(username) - ? ReusedPasswordAccountType::GMAIL - : ReusedPasswordAccountType::GSUITE); - return reused_password_account_type; - } - case PasswordType::PASSWORD_TYPE_UNKNOWN: - case PasswordType::PASSWORD_TYPE_COUNT: - reused_password_account_type.set_account_type( - ReusedPasswordAccountType::UNKNOWN); - return reused_password_account_type; - } - NOTREACHED(); - return reused_password_account_type; -} - -// static -PasswordType -PasswordProtectionServiceBase::ConvertReusedPasswordAccountTypeToPasswordType( - ReusedPasswordAccountType password_type) { - if (password_type.is_account_syncing()) { - return PasswordType::PRIMARY_ACCOUNT_PASSWORD; - } else if (password_type.account_type() == - ReusedPasswordAccountType::NON_GAIA_ENTERPRISE) { - return PasswordType::ENTERPRISE_PASSWORD; - } else if (password_type.account_type() == - ReusedPasswordAccountType::SAVED_PASSWORD) { - return PasswordType::SAVED_PASSWORD; - } else if (password_type.account_type() == - ReusedPasswordAccountType::UNKNOWN) { - return PasswordType::PASSWORD_TYPE_UNKNOWN; - } else { - return PasswordType::OTHER_GAIA_PASSWORD; - } -} - -bool PasswordProtectionServiceBase::IsSupportedPasswordTypeForPinging( - PasswordType password_type) const { - switch (password_type) { - case PasswordType::SAVED_PASSWORD: - return true; - case PasswordType::PRIMARY_ACCOUNT_PASSWORD: - return true; - case PasswordType::ENTERPRISE_PASSWORD: - return true; - case PasswordType::OTHER_GAIA_PASSWORD: - return base::FeatureList::IsEnabled( - safe_browsing::kPasswordProtectionForSignedInUsers); - case PasswordType::PASSWORD_TYPE_UNKNOWN: - case PasswordType::PASSWORD_TYPE_COUNT: - return false; - } - NOTREACHED(); - return false; -} - -bool PasswordProtectionServiceBase::IsSupportedPasswordTypeForModalWarning( - ReusedPasswordAccountType password_type) const { - if (password_type.account_type() == - ReusedPasswordAccountType::SAVED_PASSWORD && - base::FeatureList::IsEnabled( - safe_browsing::kPasswordProtectionForSavedPasswords)) - return true; - -// Currently password reuse warnings are only supported for saved passwords on -// Android. -#if defined(OS_ANDROID) - return false; -#else - if (password_type.account_type() == - ReusedPasswordAccountType::NON_GAIA_ENTERPRISE) - return true; - - if (password_type.account_type() != ReusedPasswordAccountType::GMAIL && - password_type.account_type() != ReusedPasswordAccountType::GSUITE) - return false; - - return password_type.is_account_syncing() || - base::FeatureList::IsEnabled( - safe_browsing::kPasswordProtectionForSignedInUsers); -#endif -} - -#if BUILDFLAG(SAFE_BROWSING_AVAILABLE) -void PasswordProtectionServiceBase::GetPhishingDetector( - service_manager::InterfaceProvider* provider, - mojo::Remote<mojom::PhishingDetector>* phishing_detector) { - provider->GetInterface(phishing_detector->BindNewPipeAndPassReceiver()); -} -#endif - std::unique_ptr<PasswordProtectionNavigationThrottle> PasswordProtectionService::MaybeCreateNavigationThrottle( content::NavigationHandle* navigation_handle) { @@ -566,6 +164,14 @@ return nullptr; } +#if BUILDFLAG(SAFE_BROWSING_AVAILABLE) +void PasswordProtectionService::GetPhishingDetector( + service_manager::InterfaceProvider* provider, + mojo::Remote<mojom::PhishingDetector>* phishing_detector) { + provider->GetInterface(phishing_detector->BindNewPipeAndPassReceiver()); +} +#endif + void PasswordProtectionService::RemoveWarningRequestsByWebContents( content::WebContents* web_contents) { for (auto it = warning_requests_.begin(); it != warning_requests_.end();) {
diff --git a/components/safe_browsing/content/password_protection/password_protection_service.h b/components/safe_browsing/content/password_protection/password_protection_service.h index d8db376..d81ed88 100644 --- a/components/safe_browsing/content/password_protection/password_protection_service.h +++ b/components/safe_browsing/content/password_protection/password_protection_service.h
@@ -5,96 +5,36 @@ #ifndef COMPONENTS_SAFE_BROWSING_CONTENT_PASSWORD_PROTECTION_PASSWORD_PROTECTION_SERVICE_H_ #define COMPONENTS_SAFE_BROWSING_CONTENT_PASSWORD_PROTECTION_PASSWORD_PROTECTION_SERVICE_H_ -#include <set> -#include <unordered_map> - -#include "base/callback.h" -#include "base/feature_list.h" -#include "base/gtest_prod_util.h" -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "base/memory/weak_ptr.h" -#include "base/scoped_observation.h" -#include "base/task/cancelable_task_tracker.h" -#include "base/values.h" #include "build/build_config.h" -#include "components/history/core/browser/history_service.h" -#include "components/history/core/browser/history_service_observer.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/password_reuse_detector.h" #include "components/safe_browsing/buildflags.h" #include "components/safe_browsing/content/common/safe_browsing.mojom.h" -#include "components/safe_browsing/core/browser/referrer_chain_provider.h" -#include "components/safe_browsing/core/common/safe_browsing_prefs.h" -#include "components/safe_browsing/core/db/v4_protocol_manager_util.h" +#include "components/safe_browsing/content/password_protection/password_protection_service_base.h" #include "components/safe_browsing/core/password_protection/metrics_util.h" #include "components/safe_browsing/core/proto/csd.pb.h" -#include "components/sessions/core/session_id.h" -#include "components/signin/public/identity_manager/account_info.h" #include "mojo/public/cpp/bindings/remote.h" -#include "services/network/public/cpp/shared_url_loader_factory.h" -#include "services/service_manager/public/cpp/interface_provider.h" -#include "third_party/protobuf/src/google/protobuf/repeated_field.h" namespace content { -class WebContents; class NavigationHandle; -} - -namespace policy { -class BrowserPolicyConnector; -} +class WebContents; +} // namespace content class GURL; -class HostContentSettingsMap; namespace safe_browsing { class PasswordProtectionNavigationThrottle; class PasswordProtectionRequest; -class SafeBrowsingDatabaseManager; using ReusedPasswordAccountType = LoginReputationClientRequest::PasswordReuseEvent::ReusedPasswordAccountType; -using ReusedPasswordType = - LoginReputationClientRequest::PasswordReuseEvent::ReusedPasswordType; using password_manager::metrics_util::PasswordType; -// Manage password protection pings and verdicts. There is one instance of this -// class per profile. Therefore, every PasswordProtectionServiceBase instance is -// associated with a unique HistoryService instance and a unique -// HostContentSettingsMap instance. -class PasswordProtectionServiceBase : public history::HistoryServiceObserver { +class PasswordProtectionService : public PasswordProtectionServiceBase { + using PasswordProtectionServiceBase::PasswordProtectionServiceBase; + public: - PasswordProtectionServiceBase( - const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager, - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, - history::HistoryService* history_service); - - ~PasswordProtectionServiceBase() override; - - base::WeakPtr<PasswordProtectionServiceBase> GetWeakPtr() { - return weak_factory_.GetWeakPtr(); - } - - // Looks up |settings| to find the cached verdict response. If verdict is not - // available or is expired, return VERDICT_TYPE_UNSPECIFIED. Can be called on - // any thread. - virtual LoginReputationClientResponse::VerdictType GetCachedVerdict( - const GURL& url, - LoginReputationClientRequest::TriggerType trigger_type, - ReusedPasswordAccountType password_type, - LoginReputationClientResponse* out_response); - - // Stores |verdict| in |settings| based on its |trigger_type|, |url|, - // reused |password_type|, |verdict| and |receive_time|. - virtual void CacheVerdict( - const GURL& url, - LoginReputationClientRequest::TriggerType trigger_type, - ReusedPasswordAccountType password_type, - const LoginReputationClientResponse& verdict, - const base::Time& receive_time); - // Creates an instance of PasswordProtectionRequest and call Start() on that // instance. This function also insert this request object in |requests_| for // record keeping. @@ -132,356 +72,28 @@ virtual void MaybeLogPasswordReuseDetectedEvent( content::WebContents* web_contents) = 0; - // If we want to show password reuse modal warning. - bool ShouldShowModalWarning( - LoginReputationClientRequest::TriggerType trigger_type, - ReusedPasswordAccountType password_type, - LoginReputationClientResponse::VerdictType verdict_type); - - // Shows modal warning dialog on the current |web_contents| and pass the - // |verdict_token| to callback of this dialog. - virtual void ShowModalWarning( - PasswordProtectionRequest* request, - LoginReputationClientResponse::VerdictType verdict_type, - const std::string& verdict_token, - ReusedPasswordAccountType password_type) = 0; + // Records a Chrome Sync event for the result of the URL reputation lookup + // if the user enters their sync password on a website. + virtual void MaybeLogPasswordReuseLookupEvent( + content::WebContents* web_contents, + RequestOutcome outcome, + PasswordType password_type, + const LoginReputationClientResponse* response) = 0; // Shows chrome://reset-password interstitial. - virtual void ShowInterstitial(content::WebContents* web_contens, + virtual void ShowInterstitial(content::WebContents* web_contents, ReusedPasswordAccountType password_type) = 0; -// The following functions are disabled on Android, because enterprise reporting -// extension is not supported. -#if !defined(OS_ANDROID) - // Triggers the safeBrowsingPrivate.OnPolicySpecifiedPasswordReuseDetected. - virtual void MaybeReportPasswordReuseDetected( - PasswordProtectionRequest* request, - const std::string& username, - PasswordType password_type, - bool is_phishing_url) = 0; - - // Called when a protected password change is detected. Must be called on - // UI thread. - virtual void ReportPasswordChanged() = 0; -#endif - virtual void UpdateSecurityState(safe_browsing::SBThreatType threat_type, ReusedPasswordAccountType password_type, content::WebContents* web_contents) = 0; - scoped_refptr<SafeBrowsingDatabaseManager> database_manager(); - - // Safe Browsing backend cannot get a reliable reputation of a URL if - // (1) URL is not valid - // (2) URL doesn't have http or https scheme - // (3) It maps to a local host. - // (4) Its hostname is an IP Address in an IANA-reserved range. - // (5) Its hostname is a not-yet-assigned by ICANN gTLD. - // (6) Its hostname is a dotless domain. - static bool CanGetReputationOfURL(const GURL& url); - - // If user has clicked through any Safe Browsing interstitial for |request|'s - // web contents. - virtual bool UserClickedThroughSBInterstitial( - PasswordProtectionRequest* request) = 0; - - // Returns if the warning UI is enabled. - bool IsWarningEnabled(ReusedPasswordAccountType password_type); - - // Returns the pref value of password protection warning trigger. - virtual PasswordProtectionTrigger GetPasswordProtectionWarningTriggerPref( - ReusedPasswordAccountType password_type) const = 0; - - // If |url| matches Safe Browsing allowlist domains, password protection - // change password URL, or password protection login URLs in the enterprise - // policy. - virtual bool IsURLAllowlistedForPasswordEntry(const GURL& url) const = 0; - - // Persist the phished saved password credential in the "compromised - // credentials" table. Calls the password store to add a row for each - // MatchingReusedCredential where the phished saved password is used on. - virtual void PersistPhishedSavedPasswordCredential( - const std::vector<password_manager::MatchingReusedCredential>& - matching_reused_credentials) = 0; - - // Remove all rows of the phished saved password credential in the - // "compromised credentials" table. Calls the password store to remove a row - // for each - virtual void RemovePhishedSavedPasswordCredential( - const std::vector<password_manager::MatchingReusedCredential>& - matching_reused_credentials) = 0; - #if defined(OS_ANDROID) // Returns the referring app info that starts the activity. virtual LoginReputationClientRequest::ReferringAppInfo GetReferringAppInfo( content::WebContents* web_contents) = 0; #endif - // Converts from password::metrics_util::PasswordType to - // LoginReputationClientRequest::PasswordReuseEvent::ReusedPasswordType. - static ReusedPasswordType GetPasswordProtectionReusedPasswordType( - PasswordType password_type); - - // Converts from password_manager::metrics_util::PasswordType - // to PasswordReuseEvent::ReusedPasswordAccountType. |username| is only - // used if |password_type| is OTHER_GAIA_PASSWORD because it needs to be - // compared to the list of signed in accounts. - ReusedPasswordAccountType GetPasswordProtectionReusedPasswordAccountType( - PasswordType password_type, - const std::string& username) const; - - // Converts from ReusedPasswordAccountType to - // password_manager::metrics_util::PasswordType. - static PasswordType ConvertReusedPasswordAccountTypeToPasswordType( - ReusedPasswordAccountType password_type); - - // If we can send ping for this type of reused password. - bool IsSupportedPasswordTypeForPinging(PasswordType password_type) const; - - // If we can show modal warning for this type of reused password. - bool IsSupportedPasswordTypeForModalWarning( - ReusedPasswordAccountType password_type) const; - - const ReusedPasswordAccountType& - reused_password_account_type_for_last_shown_warning() const { - return reused_password_account_type_for_last_shown_warning_; - } -#if defined(UNIT_TEST) - void set_reused_password_account_type_for_last_shown_warning( - ReusedPasswordAccountType - reused_password_account_type_for_last_shown_warning) { - reused_password_account_type_for_last_shown_warning_ = - reused_password_account_type_for_last_shown_warning; - } -#endif - - const std::string& username_for_last_shown_warning() const { - return username_for_last_shown_warning_; - } -#if defined(UNIT_TEST) - void set_username_for_last_shown_warning(const std::string& username) { - username_for_last_shown_warning_ = username; - } -#endif - - const std::vector<password_manager::MatchingReusedCredential>& - saved_passwords_matching_reused_credentials() const { - return saved_passwords_matching_reused_credentials_; - } -#if defined(UNIT_TEST) - void set_saved_passwords_matching_reused_credentials( - const std::vector<password_manager::MatchingReusedCredential>& - credentials) { - saved_passwords_matching_reused_credentials_ = credentials; - } -#endif - const std::vector<std::string>& saved_passwords_matching_domains() const { - return saved_passwords_matching_domains_; - } -#if defined(UNIT_TEST) - void set_saved_passwords_matching_domains( - const std::vector<std::string>& matching_domains) { - saved_passwords_matching_domains_ = matching_domains; - } -#endif - - virtual AccountInfo GetAccountInfo() const = 0; - - // Returns the URL where PasswordProtectionRequest instances send requests. - static GURL GetPasswordProtectionRequestUrl(); - - protected: - friend class PasswordProtectionRequest; - friend class PasswordProtectionRequestContent; - - // Chrome can send password protection ping if it is allowed by for the - // |trigger_type| and |password_type| and if Safe Browsing can compute - // reputation of |main_frame_url| (e.g. Safe Browsing is not able to compute - // reputation of a private IP or a local host). - bool CanSendPing(LoginReputationClientRequest::TriggerType trigger_type, - const GURL& main_frame_url, - ReusedPasswordAccountType password_type); - - // Called by a PasswordProtectionRequest instance when it finishes to remove - // itself from |requests_|. - virtual void RequestFinished( - PasswordProtectionRequest* request, - RequestOutcome outcome, - std::unique_ptr<LoginReputationClientResponse> response); - - // Called by a PasswordProtectionRequest instance to check if a sample ping - // can be sent to Safe Browsing. - virtual bool CanSendSamplePing() = 0; - - // Sanitize referrer chain by only keeping origin information of all URLs. - virtual void SanitizeReferrerChain(ReferrerChain* referrer_chain) = 0; - - // Cancels all requests in |requests_|, empties it, and releases references to - // the requests. - void CancelPendingRequests(); - - // Gets the total number of verdicts of the specified |trigger_type| we cached - // for this profile. This counts both expired and active verdicts. - virtual int GetStoredVerdictCount( - LoginReputationClientRequest::TriggerType trigger_type); - - // Gets an unowned |BrowserPolicyConnector| for the current platform. - virtual const policy::BrowserPolicyConnector* GetBrowserPolicyConnector() - const = 0; - - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory() { - return url_loader_factory_; - } - - // Gets the request timeout in milliseconds. - static int GetRequestTimeoutInMS(); - - // Obtains referrer chain of |event_url| and |event_tab_id| and adds this - // info into |frame|. - virtual void FillReferrerChain( - const GURL& event_url, - SessionID - event_tab_id, // SessionID::InvalidValue() if tab not available. - LoginReputationClientRequest::Frame* frame) = 0; - - void FillUserPopulation( - LoginReputationClientRequest::TriggerType trigger_type, - LoginReputationClientRequest* request_proto); - - virtual bool IsExtendedReporting() = 0; - - virtual bool IsEnhancedProtection() = 0; - - virtual bool IsIncognito() = 0; - - virtual bool IsUserMBBOptedIn() = 0; - - virtual bool IsInPasswordAlertMode( - ReusedPasswordAccountType password_type) = 0; - - virtual bool IsPingingEnabled( - LoginReputationClientRequest::TriggerType trigger_type, - ReusedPasswordAccountType password_type) = 0; - - virtual bool IsHistorySyncEnabled() = 0; - - // If primary account is syncing. - virtual bool IsPrimaryAccountSyncing() const = 0; - - // If primary account is signed in. - virtual bool IsPrimaryAccountSignedIn() const = 0; - - // If a domain is not defined for the primary account. This means the primary - // account is a Gmail account. - virtual bool IsPrimaryAccountGmail() const = 0; - - // If the domain for the non sync account is equal to |kNoHostedDomainFound|, - // this means that the account is a Gmail account. - virtual bool IsOtherGaiaAccountGmail(const std::string& username) const = 0; - - // Gets the account based off of the username from a list of signed in - // accounts. - virtual AccountInfo GetSignedInNonSyncAccount( - const std::string& username) const = 0; - -#if BUILDFLAG(FULL_SAFE_BROWSING) - virtual bool IsUnderAdvancedProtection() = 0; -#endif - - // If Safe browsing endpoint is not enabled in the country. - virtual bool IsInExcludedCountry() = 0; - - // Records a Chrome Sync event for the result of the URL reputation lookup - // if the user enters their sync password on a website. - virtual void MaybeLogPasswordReuseLookupEvent( - content::WebContents* web_contents, - RequestOutcome, - PasswordType password_type, - const LoginReputationClientResponse*) = 0; - - // Determines if we should show chrome://reset-password interstitial based on - // the reused |password_type| and the |main_frame_url|. - virtual bool CanShowInterstitial(ReusedPasswordAccountType password_type, - const GURL& main_frame_url) = 0; - - void CheckCsdAllowlistOnIOThread(const GURL& url, bool* check_result); - - // Gets the type of sync account associated with current profile or - // |NOT_SIGNED_IN|. - virtual LoginReputationClientRequest::PasswordReuseEvent::SyncAccountType - GetSyncAccountType() const = 0; - - // Get information about Delayed Warnings and Omnibox URL display experiments. - // This information is sent in PhishGuard pings. - virtual LoginReputationClientRequest::UrlDisplayExperiment - GetUrlDisplayExperiment() const = 0; - - // Returns the reason why a ping is not sent based on the |trigger_type|, - // |url| and |password_type|. Crash if |CanSendPing| is true. - virtual RequestOutcome GetPingNotSentReason( - LoginReputationClientRequest::TriggerType trigger_type, - const GURL& url, - ReusedPasswordAccountType password_type) = 0; - - const std::list<std::string>& common_spoofed_domains() const { - return common_spoofed_domains_; - } - - // Subclasses may override this method to either cancel or resume deferred - // navigations. By default, deferred navigations are not handled. - virtual void MaybeHandleDeferredNavigations( - PasswordProtectionRequest* request) {} - - // Set of pending PasswordProtectionRequests that are still waiting for - // verdict. - std::set<scoped_refptr<PasswordProtectionRequest>> pending_requests_; - - // Set of PasswordProtectionRequests that are triggering modal warnings. - std::set<scoped_refptr<PasswordProtectionRequest>> warning_requests_; - - private: - friend class PasswordProtectionServiceTest; - friend class TestPasswordProtectionService; - friend class ChromePasswordProtectionServiceTest; - friend class ChromePasswordProtectionServiceBrowserTest; - FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, - TestParseInvalidVerdictEntry); - FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, - TestParseValidVerdictEntry); - FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, - TestPathVariantsMatchCacheExpression); - FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, - TestRemoveCachedVerdictOnURLsDeleted); - FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, - TestCleanUpExpiredVerdict); - - // Overridden from history::HistoryServiceObserver. - void OnURLsDeleted(history::HistoryService* history_service, - const history::DeletionInfo& deletion_info) override; - - void HistoryServiceBeingDeleted( - history::HistoryService* history_service) override; - - // Posted to UI thread by OnURLsDeleted(...). This function remove the related - // entries in kSafeBrowsingUnhandledSyncPasswordReuses. - virtual void RemoveUnhandledSyncPasswordReuseOnURLsDeleted( - bool all_history, - const history::URLRows& deleted_rows) = 0; - - static bool PathVariantsMatchCacheExpression( - const std::vector<std::string>& generated_paths, - const std::string& cache_expression_path); - - void RecordNoPingingReason( - LoginReputationClientRequest::TriggerType trigger_type, - RequestOutcome reason, - PasswordType password_type); - -#if BUILDFLAG(FULL_SAFE_BROWSING) - // Get the content area size of current browsing window. - virtual gfx::Size GetCurrentContentAreaSize() const = 0; -#endif - #if BUILDFLAG(SAFE_BROWSING_AVAILABLE) // Binds the |phishing_detector| to the appropriate interface, as provided by // |provider|. @@ -490,47 +102,6 @@ mojo::Remote<mojom::PhishingDetector>* phishing_detector); #endif - // The username of the account which password has been reused on. It is only - // set once a modal warning or interstitial is verified to be shown. - std::string username_for_last_shown_warning_ = ""; - - // The last ReusedPasswordAccountType that was shown a warning or - // interstitial. - ReusedPasswordAccountType - reused_password_account_type_for_last_shown_warning_; - - std::vector<password_manager::MatchingReusedCredential> - saved_passwords_matching_reused_credentials_; - - std::vector<std::string> saved_passwords_matching_domains_; - - scoped_refptr<SafeBrowsingDatabaseManager> database_manager_; - - // The context we use to issue network requests. This request_context_getter - // is obtained from SafeBrowsingService so that we can use the Safe Browsing - // cookie store. - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; - - // List of most commonly spoofed domains to default to on the password warning - // dialog. - std::list<std::string> common_spoofed_domains_; - - base::ScopedObservation<history::HistoryService, - history::HistoryServiceObserver> - history_service_observation_{this}; - - // Weakptr can only cancel task if it is posted to the same thread. Therefore, - // we need CancelableTaskTracker to cancel tasks posted to IO thread. - base::CancelableTaskTracker tracker_; - - base::WeakPtrFactory<PasswordProtectionServiceBase> weak_factory_{this}; - DISALLOW_COPY_AND_ASSIGN(PasswordProtectionServiceBase); -}; - -class PasswordProtectionService : public PasswordProtectionServiceBase { - using PasswordProtectionServiceBase::PasswordProtectionServiceBase; - - public: // Called when a new navigation is starting. Create throttle if there is a // pending sync password reuse ping or if there is a modal warning dialog // showing in the corresponding web contents.
diff --git a/components/safe_browsing/content/password_protection/password_protection_service_base.cc b/components/safe_browsing/content/password_protection/password_protection_service_base.cc new file mode 100644 index 0000000..aacb5e8 --- /dev/null +++ b/components/safe_browsing/content/password_protection/password_protection_service_base.cc
@@ -0,0 +1,417 @@ +// 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 "components/safe_browsing/content/password_protection/password_protection_service_base.h" + +#include <stddef.h> + +#include <memory> +#include <string> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/metrics/histogram_macros.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "components/password_manager/core/browser/password_manager_metrics_util.h" +#include "components/password_manager/core/browser/password_reuse_detector.h" +#include "components/safe_browsing/content/password_protection/password_protection_request.h" +#include "components/safe_browsing/core/common/thread_utils.h" +#include "components/safe_browsing/core/common/utils.h" +#include "components/safe_browsing/core/db/database_manager.h" +#include "components/safe_browsing/core/features.h" +#include "google_apis/google_api_keys.h" +#include "net/base/escape.h" +#include "net/base/url_util.h" + +using password_manager::metrics_util::PasswordType; + +namespace safe_browsing { + +using PasswordReuseEvent = LoginReputationClientRequest::PasswordReuseEvent; + +namespace { + +// Keys for storing password protection verdict into a DictionaryValue. +const int kRequestTimeoutMs = 10000; +const char kPasswordProtectionRequestUrl[] = + "https://sb-ssl.google.com/safebrowsing/clientreport/login"; + +} // namespace + +PasswordProtectionServiceBase::PasswordProtectionServiceBase( + const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager, + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + history::HistoryService* history_service) + : database_manager_(database_manager), + url_loader_factory_(url_loader_factory) { + DCHECK(CurrentlyOnThread(ThreadID::UI)); + if (history_service) + history_service_observation_.Observe(history_service); + + common_spoofed_domains_ = {"login.live.com", "facebook.com", "box.com", + "google.com", "paypal.com", "apple.com", + "yahoo.com", "adobe.com", "amazon.com", + "linkedin.com"}; +} + +PasswordProtectionServiceBase::~PasswordProtectionServiceBase() { + tracker_.TryCancelAll(); + CancelPendingRequests(); + history_service_observation_.Reset(); + weak_factory_.InvalidateWeakPtrs(); +} + +// static +bool PasswordProtectionServiceBase::CanGetReputationOfURL(const GURL& url) { + if (!safe_browsing::CanGetReputationOfUrl(url)) { + return false; + } + const std::string hostname = url.HostNoBrackets(); + return !net::IsHostnameNonUnique(hostname); +} + +bool PasswordProtectionServiceBase::ShouldShowModalWarning( + LoginReputationClientRequest::TriggerType trigger_type, + ReusedPasswordAccountType password_type, + LoginReputationClientResponse::VerdictType verdict_type) { + if (trigger_type != LoginReputationClientRequest::PASSWORD_REUSE_EVENT || + !IsSupportedPasswordTypeForModalWarning(password_type)) { + return false; + } + + return (verdict_type == LoginReputationClientResponse::PHISHING || + verdict_type == LoginReputationClientResponse::LOW_REPUTATION) && + IsWarningEnabled(password_type); +} + +LoginReputationClientResponse::VerdictType +PasswordProtectionServiceBase::GetCachedVerdict( + const GURL& url, + LoginReputationClientRequest::TriggerType trigger_type, + ReusedPasswordAccountType password_type, + LoginReputationClientResponse* out_response) { + return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; +} + +void PasswordProtectionServiceBase::CacheVerdict( + const GURL& url, + LoginReputationClientRequest::TriggerType trigger_type, + ReusedPasswordAccountType password_type, + const LoginReputationClientResponse& verdict, + const base::Time& receive_time) {} + +bool PasswordProtectionServiceBase::CanSendPing( + LoginReputationClientRequest::TriggerType trigger_type, + const GURL& main_frame_url, + ReusedPasswordAccountType password_type) { + return IsPingingEnabled(trigger_type, password_type) && + !IsURLAllowlistedForPasswordEntry(main_frame_url) && + !IsInExcludedCountry(); +} + +void PasswordProtectionServiceBase::RequestFinished( + PasswordProtectionRequest* request, + RequestOutcome outcome, + std::unique_ptr<LoginReputationClientResponse> response) { + DCHECK(CurrentlyOnThread(ThreadID::UI)); + DCHECK(request); + + if (response) { + ReusedPasswordAccountType password_type = + GetPasswordProtectionReusedPasswordAccountType(request->password_type(), + request->username()); + if (outcome != RequestOutcome::RESPONSE_ALREADY_CACHED) { + CacheVerdict(request->main_frame_url(), request->trigger_type(), + password_type, *response, base::Time::Now()); + } + bool enable_warning_for_non_sync_users = base::FeatureList::IsEnabled( + safe_browsing::kPasswordProtectionForSignedInUsers); + if (!enable_warning_for_non_sync_users && + request->password_type() == PasswordType::OTHER_GAIA_PASSWORD) { + return; + } + + // If it's password alert mode and a Gsuite/enterprise account, we do not + // show a modal warning. + if (outcome == RequestOutcome::PASSWORD_ALERT_MODE && + (password_type.account_type() == ReusedPasswordAccountType::GSUITE || + password_type.account_type() == + ReusedPasswordAccountType::NON_GAIA_ENTERPRISE)) { + return; + } + + if (ShouldShowModalWarning(request->trigger_type(), password_type, + response->verdict_type())) { + username_for_last_shown_warning_ = request->username(); + reused_password_account_type_for_last_shown_warning_ = password_type; + saved_passwords_matching_domains_ = request->matching_domains(); + ShowModalWarning(request, response->verdict_type(), + response->verdict_token(), password_type); + request->set_is_modal_warning_showing(true); + } + } + + MaybeHandleDeferredNavigations(request); + + // If the request is canceled, the PasswordProtectionServiceBase is already + // partially destroyed, and we won't be able to log accurate metrics. + if (outcome != RequestOutcome::CANCELED) { + auto verdict = + response ? response->verdict_type() + : LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; + +// Disabled on Android, because enterprise reporting extension is not supported. +#if !defined(OS_ANDROID) + MaybeReportPasswordReuseDetected( + request, request->username(), request->password_type(), + verdict == LoginReputationClientResponse::PHISHING); +#endif + + // Persist a bit in CompromisedCredentials table when saved password is + // reused on a phishing or low reputation site. + auto is_unsafe_url = + verdict == LoginReputationClientResponse::PHISHING || + verdict == LoginReputationClientResponse::LOW_REPUTATION; + if (is_unsafe_url) { + PersistPhishedSavedPasswordCredential( + request->matching_reused_credentials()); + } + } + + // Remove request from |pending_requests_| list. If it triggers warning, add + // it into the !warning_reqeusts_| list. + for (auto it = pending_requests_.begin(); it != pending_requests_.end(); + it++) { + if (it->get() == request) { + if (request->is_modal_warning_showing()) + warning_requests_.insert(std::move(request)); + pending_requests_.erase(it); + break; + } + } +} + +void PasswordProtectionServiceBase::CancelPendingRequests() { + DCHECK(CurrentlyOnThread(ThreadID::UI)); + for (auto it = pending_requests_.begin(); it != pending_requests_.end();) { + PasswordProtectionRequest* request = it->get(); + // These are the requests for whom we're still waiting for verdicts. + // We need to advance the iterator before we cancel because canceling + // the request will invalidate it when RequestFinished is called. + it++; + request->Cancel(false); + } + DCHECK(pending_requests_.empty()); +} + +int PasswordProtectionServiceBase::GetStoredVerdictCount( + LoginReputationClientRequest::TriggerType trigger_type) { + return -1; +} + +scoped_refptr<SafeBrowsingDatabaseManager> +PasswordProtectionServiceBase::database_manager() { + return database_manager_; +} + +// static +GURL PasswordProtectionServiceBase::GetPasswordProtectionRequestUrl() { + GURL url(kPasswordProtectionRequestUrl); + std::string api_key = google_apis::GetAPIKey(); + DCHECK(!api_key.empty()); + return url.Resolve("?key=" + net::EscapeQueryParamValue(api_key, true)); +} + +// static +int PasswordProtectionServiceBase::GetRequestTimeoutInMS() { + return kRequestTimeoutMs; +} + +void PasswordProtectionServiceBase::FillUserPopulation( + LoginReputationClientRequest::TriggerType trigger_type, + LoginReputationClientRequest* request_proto) { + ChromeUserPopulation* user_population = request_proto->mutable_population(); + user_population->set_user_population( + IsEnhancedProtection() + ? ChromeUserPopulation::ENHANCED_PROTECTION + : IsExtendedReporting() ? ChromeUserPopulation::EXTENDED_REPORTING + : ChromeUserPopulation::SAFE_BROWSING); + user_population->set_profile_management_status( + GetProfileManagementStatus(GetBrowserPolicyConnector())); + user_population->set_is_history_sync_enabled(IsHistorySyncEnabled()); +#if BUILDFLAG(FULL_SAFE_BROWSING) + user_population->set_is_under_advanced_protection( + IsUnderAdvancedProtection()); +#endif + user_population->set_is_incognito(IsIncognito()); + user_population->set_is_mbb_enabled(IsUserMBBOptedIn()); +} + +void PasswordProtectionServiceBase::OnURLsDeleted( + history::HistoryService* history_service, + const history::DeletionInfo& deletion_info) { + GetTaskRunner(ThreadID::UI) + ->PostTask( + FROM_HERE, + base::BindRepeating(&PasswordProtectionServiceBase:: + RemoveUnhandledSyncPasswordReuseOnURLsDeleted, + GetWeakPtr(), deletion_info.IsAllHistory(), + deletion_info.deleted_rows())); +} + +void PasswordProtectionServiceBase::HistoryServiceBeingDeleted( + history::HistoryService* history_service) { + DCHECK(history_service_observation_.IsObservingSource(history_service)); + history_service_observation_.Reset(); +} + +bool PasswordProtectionServiceBase::IsWarningEnabled( + ReusedPasswordAccountType password_type) { + return GetPasswordProtectionWarningTriggerPref(password_type) == + PHISHING_REUSE; +} + +// static +ReusedPasswordType +PasswordProtectionServiceBase::GetPasswordProtectionReusedPasswordType( + password_manager::metrics_util::PasswordType password_type) { + switch (password_type) { + case PasswordType::SAVED_PASSWORD: + return PasswordReuseEvent::SAVED_PASSWORD; + case PasswordType::PRIMARY_ACCOUNT_PASSWORD: + return PasswordReuseEvent::SIGN_IN_PASSWORD; + case PasswordType::OTHER_GAIA_PASSWORD: + return PasswordReuseEvent::OTHER_GAIA_PASSWORD; + case PasswordType::ENTERPRISE_PASSWORD: + return PasswordReuseEvent::ENTERPRISE_PASSWORD; + case PasswordType::PASSWORD_TYPE_UNKNOWN: + return PasswordReuseEvent::REUSED_PASSWORD_TYPE_UNKNOWN; + case PasswordType::PASSWORD_TYPE_COUNT: + break; + } + NOTREACHED(); + return PasswordReuseEvent::REUSED_PASSWORD_TYPE_UNKNOWN; +} + +ReusedPasswordAccountType +PasswordProtectionServiceBase::GetPasswordProtectionReusedPasswordAccountType( + password_manager::metrics_util::PasswordType password_type, + const std::string& username) const { + ReusedPasswordAccountType reused_password_account_type; + switch (password_type) { + case PasswordType::SAVED_PASSWORD: + reused_password_account_type.set_account_type( + ReusedPasswordAccountType::SAVED_PASSWORD); + return reused_password_account_type; + case PasswordType::ENTERPRISE_PASSWORD: + reused_password_account_type.set_account_type( + ReusedPasswordAccountType::NON_GAIA_ENTERPRISE); + return reused_password_account_type; + case PasswordType::PRIMARY_ACCOUNT_PASSWORD: { + reused_password_account_type.set_is_account_syncing( + IsPrimaryAccountSyncing()); + if (!IsPrimaryAccountSignedIn()) { + reused_password_account_type.set_account_type( + ReusedPasswordAccountType::UNKNOWN); + return reused_password_account_type; + } + reused_password_account_type.set_account_type( + IsPrimaryAccountGmail() ? ReusedPasswordAccountType::GMAIL + : ReusedPasswordAccountType::GSUITE); + return reused_password_account_type; + } + case PasswordType::OTHER_GAIA_PASSWORD: { + AccountInfo account_info = GetSignedInNonSyncAccount(username); + if (account_info.account_id.empty()) { + reused_password_account_type.set_account_type( + ReusedPasswordAccountType::UNKNOWN); + return reused_password_account_type; + } + reused_password_account_type.set_account_type( + IsOtherGaiaAccountGmail(username) + ? ReusedPasswordAccountType::GMAIL + : ReusedPasswordAccountType::GSUITE); + return reused_password_account_type; + } + case PasswordType::PASSWORD_TYPE_UNKNOWN: + case PasswordType::PASSWORD_TYPE_COUNT: + reused_password_account_type.set_account_type( + ReusedPasswordAccountType::UNKNOWN); + return reused_password_account_type; + } + NOTREACHED(); + return reused_password_account_type; +} + +// static +PasswordType +PasswordProtectionServiceBase::ConvertReusedPasswordAccountTypeToPasswordType( + ReusedPasswordAccountType password_type) { + if (password_type.is_account_syncing()) { + return PasswordType::PRIMARY_ACCOUNT_PASSWORD; + } else if (password_type.account_type() == + ReusedPasswordAccountType::NON_GAIA_ENTERPRISE) { + return PasswordType::ENTERPRISE_PASSWORD; + } else if (password_type.account_type() == + ReusedPasswordAccountType::SAVED_PASSWORD) { + return PasswordType::SAVED_PASSWORD; + } else if (password_type.account_type() == + ReusedPasswordAccountType::UNKNOWN) { + return PasswordType::PASSWORD_TYPE_UNKNOWN; + } else { + return PasswordType::OTHER_GAIA_PASSWORD; + } +} + +bool PasswordProtectionServiceBase::IsSupportedPasswordTypeForPinging( + PasswordType password_type) const { + switch (password_type) { + case PasswordType::SAVED_PASSWORD: + return true; + case PasswordType::PRIMARY_ACCOUNT_PASSWORD: + return true; + case PasswordType::ENTERPRISE_PASSWORD: + return true; + case PasswordType::OTHER_GAIA_PASSWORD: + return base::FeatureList::IsEnabled( + safe_browsing::kPasswordProtectionForSignedInUsers); + case PasswordType::PASSWORD_TYPE_UNKNOWN: + case PasswordType::PASSWORD_TYPE_COUNT: + return false; + } + NOTREACHED(); + return false; +} + +bool PasswordProtectionServiceBase::IsSupportedPasswordTypeForModalWarning( + ReusedPasswordAccountType password_type) const { + if (password_type.account_type() == + ReusedPasswordAccountType::SAVED_PASSWORD && + base::FeatureList::IsEnabled( + safe_browsing::kPasswordProtectionForSavedPasswords)) + return true; + +// Currently password reuse warnings are only supported for saved passwords on +// Android. +#if defined(OS_ANDROID) + return false; +#else + if (password_type.account_type() == + ReusedPasswordAccountType::NON_GAIA_ENTERPRISE) + return true; + + if (password_type.account_type() != ReusedPasswordAccountType::GMAIL && + password_type.account_type() != ReusedPasswordAccountType::GSUITE) + return false; + + return password_type.is_account_syncing() || + base::FeatureList::IsEnabled( + safe_browsing::kPasswordProtectionForSignedInUsers); +#endif +} + +} // namespace safe_browsing
diff --git a/components/safe_browsing/content/password_protection/password_protection_service_base.h b/components/safe_browsing/content/password_protection/password_protection_service_base.h new file mode 100644 index 0000000..5291971 --- /dev/null +++ b/components/safe_browsing/content/password_protection/password_protection_service_base.h
@@ -0,0 +1,454 @@ +// 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 COMPONENTS_SAFE_BROWSING_CONTENT_PASSWORD_PROTECTION_PASSWORD_PROTECTION_SERVICE_BASE_H_ +#define COMPONENTS_SAFE_BROWSING_CONTENT_PASSWORD_PROTECTION_PASSWORD_PROTECTION_SERVICE_BASE_H_ + +#include <set> + +#include "base/feature_list.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/scoped_observation.h" +#include "base/task/cancelable_task_tracker.h" +#include "base/values.h" +#include "build/build_config.h" +#include "components/history/core/browser/history_service.h" +#include "components/history/core/browser/history_service_observer.h" +#include "components/password_manager/core/browser/password_manager_metrics_util.h" +#include "components/password_manager/core/browser/password_reuse_detector.h" +#include "components/safe_browsing/buildflags.h" +#include "components/safe_browsing/core/browser/referrer_chain_provider.h" +#include "components/safe_browsing/core/common/safe_browsing_prefs.h" +#include "components/safe_browsing/core/db/v4_protocol_manager_util.h" +#include "components/safe_browsing/core/password_protection/metrics_util.h" +#include "components/safe_browsing/core/proto/csd.pb.h" +#include "components/sessions/core/session_id.h" +#include "components/signin/public/identity_manager/account_info.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/service_manager/public/cpp/interface_provider.h" +#include "third_party/protobuf/src/google/protobuf/repeated_field.h" + +namespace policy { +class BrowserPolicyConnector; +} + +class GURL; + +namespace safe_browsing { + +class PasswordProtectionRequest; +class SafeBrowsingDatabaseManager; + +using ReusedPasswordAccountType = + LoginReputationClientRequest::PasswordReuseEvent::ReusedPasswordAccountType; +using ReusedPasswordType = + LoginReputationClientRequest::PasswordReuseEvent::ReusedPasswordType; +using password_manager::metrics_util::PasswordType; + +// Manage password protection pings and verdicts. There is one instance of this +// class per profile. Therefore, every PasswordProtectionServiceBase instance is +// associated with a unique HistoryService instance and a unique +// HostContentSettingsMap instance. +class PasswordProtectionServiceBase : public history::HistoryServiceObserver { + public: + PasswordProtectionServiceBase( + const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager, + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + history::HistoryService* history_service); + + ~PasswordProtectionServiceBase() override; + + base::WeakPtr<PasswordProtectionServiceBase> GetWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + + // Looks up |settings| to find the cached verdict response. If verdict is not + // available or is expired, return VERDICT_TYPE_UNSPECIFIED. Can be called on + // any thread. + virtual LoginReputationClientResponse::VerdictType GetCachedVerdict( + const GURL& url, + LoginReputationClientRequest::TriggerType trigger_type, + ReusedPasswordAccountType password_type, + LoginReputationClientResponse* out_response); + + // Stores |verdict| in |settings| based on its |trigger_type|, |url|, + // reused |password_type|, |verdict| and |receive_time|. + virtual void CacheVerdict( + const GURL& url, + LoginReputationClientRequest::TriggerType trigger_type, + ReusedPasswordAccountType password_type, + const LoginReputationClientResponse& verdict, + const base::Time& receive_time); + + // If we want to show password reuse modal warning. + bool ShouldShowModalWarning( + LoginReputationClientRequest::TriggerType trigger_type, + ReusedPasswordAccountType password_type, + LoginReputationClientResponse::VerdictType verdict_type); + + // Shows modal warning dialog on the current |web_contents| and pass the + // |verdict_token| to callback of this dialog. + virtual void ShowModalWarning( + PasswordProtectionRequest* request, + LoginReputationClientResponse::VerdictType verdict_type, + const std::string& verdict_token, + ReusedPasswordAccountType password_type) = 0; + +// The following functions are disabled on Android, because enterprise reporting +// extension is not supported. +#if !defined(OS_ANDROID) + // Triggers the safeBrowsingPrivate.OnPolicySpecifiedPasswordReuseDetected. + virtual void MaybeReportPasswordReuseDetected( + PasswordProtectionRequest* request, + const std::string& username, + PasswordType password_type, + bool is_phishing_url) = 0; + + // Called when a protected password change is detected. Must be called on + // UI thread. + virtual void ReportPasswordChanged() = 0; +#endif + + scoped_refptr<SafeBrowsingDatabaseManager> database_manager(); + + // Safe Browsing backend cannot get a reliable reputation of a URL if + // (1) URL is not valid + // (2) URL doesn't have http or https scheme + // (3) It maps to a local host. + // (4) Its hostname is an IP Address in an IANA-reserved range. + // (5) Its hostname is a not-yet-assigned by ICANN gTLD. + // (6) Its hostname is a dotless domain. + static bool CanGetReputationOfURL(const GURL& url); + + // If user has clicked through any Safe Browsing interstitial for |request|'s + // web contents. + virtual bool UserClickedThroughSBInterstitial( + PasswordProtectionRequest* request) = 0; + + // Returns if the warning UI is enabled. + bool IsWarningEnabled(ReusedPasswordAccountType password_type); + + // Returns the pref value of password protection warning trigger. + virtual PasswordProtectionTrigger GetPasswordProtectionWarningTriggerPref( + ReusedPasswordAccountType password_type) const = 0; + + // If |url| matches Safe Browsing allowlist domains, password protection + // change password URL, or password protection login URLs in the enterprise + // policy. + virtual bool IsURLAllowlistedForPasswordEntry(const GURL& url) const = 0; + + // Persist the phished saved password credential in the "compromised + // credentials" table. Calls the password store to add a row for each + // MatchingReusedCredential where the phished saved password is used on. + virtual void PersistPhishedSavedPasswordCredential( + const std::vector<password_manager::MatchingReusedCredential>& + matching_reused_credentials) = 0; + + // Remove all rows of the phished saved password credential in the + // "compromised credentials" table. Calls the password store to remove a row + // for each + virtual void RemovePhishedSavedPasswordCredential( + const std::vector<password_manager::MatchingReusedCredential>& + matching_reused_credentials) = 0; + + // Converts from password::metrics_util::PasswordType to + // LoginReputationClientRequest::PasswordReuseEvent::ReusedPasswordType. + static ReusedPasswordType GetPasswordProtectionReusedPasswordType( + PasswordType password_type); + + // Converts from password_manager::metrics_util::PasswordType + // to PasswordReuseEvent::ReusedPasswordAccountType. |username| is only + // used if |password_type| is OTHER_GAIA_PASSWORD because it needs to be + // compared to the list of signed in accounts. + ReusedPasswordAccountType GetPasswordProtectionReusedPasswordAccountType( + PasswordType password_type, + const std::string& username) const; + + // Converts from ReusedPasswordAccountType to + // password_manager::metrics_util::PasswordType. + static PasswordType ConvertReusedPasswordAccountTypeToPasswordType( + ReusedPasswordAccountType password_type); + + // If we can send ping for this type of reused password. + bool IsSupportedPasswordTypeForPinging(PasswordType password_type) const; + + // If we can show modal warning for this type of reused password. + bool IsSupportedPasswordTypeForModalWarning( + ReusedPasswordAccountType password_type) const; + + const ReusedPasswordAccountType& + reused_password_account_type_for_last_shown_warning() const { + return reused_password_account_type_for_last_shown_warning_; + } +#if defined(UNIT_TEST) + void set_reused_password_account_type_for_last_shown_warning( + ReusedPasswordAccountType + reused_password_account_type_for_last_shown_warning) { + reused_password_account_type_for_last_shown_warning_ = + reused_password_account_type_for_last_shown_warning; + } +#endif + + const std::string& username_for_last_shown_warning() const { + return username_for_last_shown_warning_; + } +#if defined(UNIT_TEST) + void set_username_for_last_shown_warning(const std::string& username) { + username_for_last_shown_warning_ = username; + } +#endif + + const std::vector<password_manager::MatchingReusedCredential>& + saved_passwords_matching_reused_credentials() const { + return saved_passwords_matching_reused_credentials_; + } +#if defined(UNIT_TEST) + void set_saved_passwords_matching_reused_credentials( + const std::vector<password_manager::MatchingReusedCredential>& + credentials) { + saved_passwords_matching_reused_credentials_ = credentials; + } +#endif + const std::vector<std::string>& saved_passwords_matching_domains() const { + return saved_passwords_matching_domains_; + } +#if defined(UNIT_TEST) + void set_saved_passwords_matching_domains( + const std::vector<std::string>& matching_domains) { + saved_passwords_matching_domains_ = matching_domains; + } +#endif + + virtual AccountInfo GetAccountInfo() const = 0; + + // Returns the URL where PasswordProtectionRequest instances send requests. + static GURL GetPasswordProtectionRequestUrl(); + + protected: + friend class PasswordProtectionRequest; + friend class PasswordProtectionRequestContent; + + // Chrome can send password protection ping if it is allowed by for the + // |trigger_type| and |password_type| and if Safe Browsing can compute + // reputation of |main_frame_url| (e.g. Safe Browsing is not able to compute + // reputation of a private IP or a local host). + bool CanSendPing(LoginReputationClientRequest::TriggerType trigger_type, + const GURL& main_frame_url, + ReusedPasswordAccountType password_type); + + // Called by a PasswordProtectionRequest instance when it finishes to remove + // itself from |requests_|. + virtual void RequestFinished( + PasswordProtectionRequest* request, + RequestOutcome outcome, + std::unique_ptr<LoginReputationClientResponse> response); + + // Called by a PasswordProtectionRequest instance to check if a sample ping + // can be sent to Safe Browsing. + virtual bool CanSendSamplePing() = 0; + + // Sanitize referrer chain by only keeping origin information of all URLs. + virtual void SanitizeReferrerChain(ReferrerChain* referrer_chain) = 0; + + // Cancels all requests in |requests_|, empties it, and releases references to + // the requests. + void CancelPendingRequests(); + + // Gets the total number of verdicts of the specified |trigger_type| we cached + // for this profile. This counts both expired and active verdicts. + virtual int GetStoredVerdictCount( + LoginReputationClientRequest::TriggerType trigger_type); + + // Gets an unowned |BrowserPolicyConnector| for the current platform. + virtual const policy::BrowserPolicyConnector* GetBrowserPolicyConnector() + const = 0; + + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory() { + return url_loader_factory_; + } + + // Gets the request timeout in milliseconds. + static int GetRequestTimeoutInMS(); + + // Obtains referrer chain of |event_url| and |event_tab_id| and adds this + // info into |frame|. + virtual void FillReferrerChain( + const GURL& event_url, + SessionID + event_tab_id, // SessionID::InvalidValue() if tab not available. + LoginReputationClientRequest::Frame* frame) = 0; + + void FillUserPopulation( + LoginReputationClientRequest::TriggerType trigger_type, + LoginReputationClientRequest* request_proto); + + virtual bool IsExtendedReporting() = 0; + + virtual bool IsEnhancedProtection() = 0; + + virtual bool IsIncognito() = 0; + + virtual bool IsUserMBBOptedIn() = 0; + + virtual bool IsInPasswordAlertMode( + ReusedPasswordAccountType password_type) = 0; + + virtual bool IsPingingEnabled( + LoginReputationClientRequest::TriggerType trigger_type, + ReusedPasswordAccountType password_type) = 0; + + virtual bool IsHistorySyncEnabled() = 0; + + // If primary account is syncing. + virtual bool IsPrimaryAccountSyncing() const = 0; + + // If primary account is signed in. + virtual bool IsPrimaryAccountSignedIn() const = 0; + + // If a domain is not defined for the primary account. This means the primary + // account is a Gmail account. + virtual bool IsPrimaryAccountGmail() const = 0; + + // If the domain for the non sync account is equal to |kNoHostedDomainFound|, + // this means that the account is a Gmail account. + virtual bool IsOtherGaiaAccountGmail(const std::string& username) const = 0; + + // Gets the account based off of the username from a list of signed in + // accounts. + virtual AccountInfo GetSignedInNonSyncAccount( + const std::string& username) const = 0; + +#if BUILDFLAG(FULL_SAFE_BROWSING) + virtual bool IsUnderAdvancedProtection() = 0; +#endif + + // If Safe browsing endpoint is not enabled in the country. + virtual bool IsInExcludedCountry() = 0; + + // Determines if we should show chrome://reset-password interstitial based on + // the reused |password_type| and the |main_frame_url|. + virtual bool CanShowInterstitial(ReusedPasswordAccountType password_type, + const GURL& main_frame_url) = 0; + + void CheckCsdAllowlistOnIOThread(const GURL& url, bool* check_result); + + // Gets the type of sync account associated with current profile or + // |NOT_SIGNED_IN|. + virtual LoginReputationClientRequest::PasswordReuseEvent::SyncAccountType + GetSyncAccountType() const = 0; + + // Get information about Delayed Warnings and Omnibox URL display experiments. + // This information is sent in PhishGuard pings. + virtual LoginReputationClientRequest::UrlDisplayExperiment + GetUrlDisplayExperiment() const = 0; + + // Returns the reason why a ping is not sent based on the |trigger_type|, + // |url| and |password_type|. Crash if |CanSendPing| is true. + virtual RequestOutcome GetPingNotSentReason( + LoginReputationClientRequest::TriggerType trigger_type, + const GURL& url, + ReusedPasswordAccountType password_type) = 0; + + const std::list<std::string>& common_spoofed_domains() const { + return common_spoofed_domains_; + } + + // Subclasses may override this method to either cancel or resume deferred + // navigations. By default, deferred navigations are not handled. + virtual void MaybeHandleDeferredNavigations( + PasswordProtectionRequest* request) {} + + // Set of pending PasswordProtectionRequests that are still waiting for + // verdict. + std::set<scoped_refptr<PasswordProtectionRequest>> pending_requests_; + + // Set of PasswordProtectionRequests that are triggering modal warnings. + std::set<scoped_refptr<PasswordProtectionRequest>> warning_requests_; + + // The username of the account which password has been reused on. It is only + // set once a modal warning or interstitial is verified to be shown. + std::string username_for_last_shown_warning_ = ""; + + // The last ReusedPasswordAccountType that was shown a warning or + // interstitial. + ReusedPasswordAccountType + reused_password_account_type_for_last_shown_warning_; + + std::vector<password_manager::MatchingReusedCredential> + saved_passwords_matching_reused_credentials_; + + private: + friend class PasswordProtectionServiceTest; + friend class TestPasswordProtectionService; + friend class ChromePasswordProtectionServiceTest; + friend class ChromePasswordProtectionServiceBrowserTest; + FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, + TestParseInvalidVerdictEntry); + FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, + TestParseValidVerdictEntry); + FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, + TestPathVariantsMatchCacheExpression); + FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, + TestRemoveCachedVerdictOnURLsDeleted); + FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, + TestCleanUpExpiredVerdict); + + // Overridden from history::HistoryServiceObserver. + void OnURLsDeleted(history::HistoryService* history_service, + const history::DeletionInfo& deletion_info) override; + + void HistoryServiceBeingDeleted( + history::HistoryService* history_service) override; + + // Posted to UI thread by OnURLsDeleted(...). This function remove the related + // entries in kSafeBrowsingUnhandledSyncPasswordReuses. + virtual void RemoveUnhandledSyncPasswordReuseOnURLsDeleted( + bool all_history, + const history::URLRows& deleted_rows) = 0; + + static bool PathVariantsMatchCacheExpression( + const std::vector<std::string>& generated_paths, + const std::string& cache_expression_path); + + void RecordNoPingingReason( + LoginReputationClientRequest::TriggerType trigger_type, + RequestOutcome reason, + PasswordType password_type); + +#if BUILDFLAG(FULL_SAFE_BROWSING) + // Get the content area size of current browsing window. + virtual gfx::Size GetCurrentContentAreaSize() const = 0; +#endif + + std::vector<std::string> saved_passwords_matching_domains_; + + scoped_refptr<SafeBrowsingDatabaseManager> database_manager_; + + // The context we use to issue network requests. This request_context_getter + // is obtained from SafeBrowsingService so that we can use the Safe Browsing + // cookie store. + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; + + // List of most commonly spoofed domains to default to on the password warning + // dialog. + std::list<std::string> common_spoofed_domains_; + + base::ScopedObservation<history::HistoryService, + history::HistoryServiceObserver> + history_service_observation_{this}; + + // Weakptr can only cancel task if it is posted to the same thread. Therefore, + // we need CancelableTaskTracker to cancel tasks posted to IO thread. + base::CancelableTaskTracker tracker_; + + base::WeakPtrFactory<PasswordProtectionServiceBase> weak_factory_{this}; + DISALLOW_COPY_AND_ASSIGN(PasswordProtectionServiceBase); +}; + +} // namespace safe_browsing + +#endif // COMPONENTS_SAFE_BROWSING_CONTENT_PASSWORD_PROTECTION_PASSWORD_PROTECTION_SERVICE_BASE_H_
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc index 5746b04..a0f471e 100644 --- a/components/viz/common/features.cc +++ b/components/viz/common/features.cc
@@ -32,8 +32,9 @@ // Use the SkiaRenderer. const base::Feature kUseSkiaRenderer { "UseSkiaRenderer", -#if defined(OS_WIN) || (defined(OS_LINUX) && !(BUILDFLAG(IS_CHROMEOS_ASH) || \ - BUILDFLAG(IS_CHROMECAST))) +#if defined(OS_WIN) || defined(OS_ANDROID) || \ + (defined(OS_LINUX) && \ + !(BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMECAST))) base::FEATURE_ENABLED_BY_DEFAULT #else base::FEATURE_DISABLED_BY_DEFAULT
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn index 0f6a5a2..0e237c0 100644 --- a/components/viz/service/BUILD.gn +++ b/components/viz/service/BUILD.gn
@@ -28,6 +28,8 @@ "display/delegated_ink_point_renderer_base.h", "display/delegated_ink_point_renderer_skia.cc", "display/delegated_ink_point_renderer_skia.h", + "display/delegated_ink_trail_data.cc", + "display/delegated_ink_trail_data.h", "display/direct_renderer.cc", "display/direct_renderer.h", "display/display.cc",
diff --git a/components/viz/service/display/delegated_ink_point_pixel_test_helper.cc b/components/viz/service/display/delegated_ink_point_pixel_test_helper.cc index ad8fe95..30c6f30 100644 --- a/components/viz/service/display/delegated_ink_point_pixel_test_helper.cc +++ b/components/viz/service/display/delegated_ink_point_pixel_test_helper.cc
@@ -36,52 +36,76 @@ const gfx::PointF& point, float diameter, SkColor color, + base::TimeTicks timestamp, const gfx::RectF& presentation_area) { DCHECK(renderer_); - metadata_ = DelegatedInkMetadata(point, diameter, color, - base::TimeTicks::Now(), presentation_area, - base::TimeTicks::Now(), /*hovering*/ false); + metadata_ = + DelegatedInkMetadata(point, diameter, color, timestamp, presentation_area, + base::TimeTicks::Now(), /*hovering*/ false); GetInkRenderer()->SetDelegatedInkMetadata( std::make_unique<DelegatedInkMetadata>(metadata_)); } void DelegatedInkPointPixelTestHelper::CreateAndSendMetadataFromLastPoint() { - DCHECK(renderer_); - metadata_ = DelegatedInkMetadata( - ink_points_.back().point(), metadata_.diameter(), metadata_.color(), - ink_points_.back().timestamp(), metadata_.presentation_area(), - metadata_.frame_time(), metadata_.is_hovering()); - GetInkRenderer()->SetDelegatedInkMetadata( - std::make_unique<DelegatedInkMetadata>(metadata_)); + DCHECK_EQ(static_cast<int>(ink_points_.size()), 1); + CreateAndSendMetadataFromLastPoint(ink_points_.begin()->first); +} + +void DelegatedInkPointPixelTestHelper::CreateAndSendMetadataFromLastPoint( + int32_t pointer_id) { + DCHECK(ink_points_.find(pointer_id) != ink_points_.end()); + CreateAndSendMetadata(ink_points_[pointer_id].back().point(), + metadata_.diameter(), metadata_.color(), + ink_points_[pointer_id].back().timestamp(), + metadata_.presentation_area()); } void DelegatedInkPointPixelTestHelper::CreateAndSendPoint( const gfx::PointF& point, base::TimeTicks timestamp) { - DCHECK(renderer_); - constexpr int32_t kPointerId = 1; - ink_points_.emplace_back(point, timestamp, kPointerId); - GetInkRenderer()->StoreDelegatedInkPoint(ink_points_.back()); + CreateAndSendPoint(point, timestamp, /*pointer_id*/ 1); } -void DelegatedInkPointPixelTestHelper::CreateAndSendPointFromMetadata() { - CreateAndSendPoint(metadata().point(), metadata().timestamp()); +void DelegatedInkPointPixelTestHelper::CreateAndSendPoint( + const gfx::PointF& point, + base::TimeTicks timestamp, + int32_t pointer_id) { + DCHECK(renderer_); + ink_points_[pointer_id].emplace_back(point, timestamp, pointer_id); + GetInkRenderer()->StoreDelegatedInkPoint(ink_points_[pointer_id].back()); } void DelegatedInkPointPixelTestHelper::CreateAndSendPointFromLastPoint( const gfx::PointF& point) { - EXPECT_GT(static_cast<int>(ink_points_.size()), 0); - CreateAndSendPoint(point, ink_points_.back().timestamp() + - base::TimeDelta::FromMicroseconds(10)); + DCHECK_EQ(static_cast<int>(ink_points_.size()), 1); + CreateAndSendPointFromLastPoint(ink_points_.begin()->first, point); +} + +void DelegatedInkPointPixelTestHelper::CreateAndSendPointFromLastPoint( + int32_t pointer_id, + const gfx::PointF& point) { + DCHECK(ink_points_.find(pointer_id) != ink_points_.end()); + EXPECT_GT(static_cast<int>(ink_points_[pointer_id].size()), 0); + CreateAndSendPoint(point, + ink_points_[pointer_id].back().timestamp() + + base::TimeDelta::FromMicroseconds(10), + pointer_id); } gfx::Rect DelegatedInkPointPixelTestHelper::GetDelegatedInkDamageRect() { - EXPECT_GT(static_cast<int>(ink_points_.size()), 0); + DCHECK_EQ(static_cast<int>(ink_points_.size()), 1); + return GetDelegatedInkDamageRect(ink_points_.begin()->first); +} + +gfx::Rect DelegatedInkPointPixelTestHelper::GetDelegatedInkDamageRect( + int32_t pointer_id) { + DCHECK(ink_points_.find(pointer_id) != ink_points_.end()); + EXPECT_GT(static_cast<int>(ink_points_[pointer_id].size()), 0); gfx::RectF ink_damage_rect_f = - gfx::RectF(ink_points_[0].point(), gfx::SizeF(1, 1)); - for (uint64_t i = 1; i < ink_points_.size(); ++i) { + gfx::RectF(ink_points_[pointer_id][0].point(), gfx::SizeF(1, 1)); + for (uint64_t i = 1; i < ink_points_[pointer_id].size(); ++i) { ink_damage_rect_f.Union( - gfx::RectF(ink_points_[i].point(), gfx::SizeF(1, 1))); + gfx::RectF(ink_points_[pointer_id][i].point(), gfx::SizeF(1, 1))); } ink_damage_rect_f.Inset(-metadata().diameter() / 2.f, -metadata().diameter() / 2.f);
diff --git a/components/viz/service/display/delegated_ink_point_pixel_test_helper.h b/components/viz/service/display/delegated_ink_point_pixel_test_helper.h index c9003de..c172238b 100644 --- a/components/viz/service/display/delegated_ink_point_pixel_test_helper.h +++ b/components/viz/service/display/delegated_ink_point_pixel_test_helper.h
@@ -5,6 +5,7 @@ #ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_DELEGATED_INK_POINT_PIXEL_TEST_HELPER_H_ #define COMPONENTS_VIZ_SERVICE_DISPLAY_DELEGATED_INK_POINT_PIXEL_TEST_HELPER_H_ +#include <unordered_map> #include <vector> #include "components/viz/common/delegated_ink_metadata.h" @@ -35,23 +36,31 @@ void CreateAndSendMetadata(const gfx::PointF& point, float diameter, SkColor color, + base::TimeTicks timestamp, const gfx::RectF& presentation_area); void CreateAndSendMetadataFromLastPoint(); + void CreateAndSendMetadataFromLastPoint(int32_t pointer_id); void CreateAndSendPoint(const gfx::PointF& point, base::TimeTicks timestamp); - void CreateAndSendPointFromMetadata(); + void CreateAndSendPoint(const gfx::PointF& point, + base::TimeTicks timestamp, + int32_t pointer_id); + // Used when sending multiple points to be drawn as a single trail, so it uses // the most recently provided point's timestamp to determine the new one. void CreateAndSendPointFromLastPoint(const gfx::PointF& point); + void CreateAndSendPointFromLastPoint(int32_t pointer_id, + const gfx::PointF& point); gfx::Rect GetDelegatedInkDamageRect(); + gfx::Rect GetDelegatedInkDamageRect(int32_t pointer_id); const DelegatedInkMetadata& metadata() { return metadata_; } private: DirectRenderer* renderer_ = nullptr; - std::vector<DelegatedInkPoint> ink_points_; + std::unordered_map<int32_t, std::vector<DelegatedInkPoint>> ink_points_; DelegatedInkMetadata metadata_; };
diff --git a/components/viz/service/display/delegated_ink_point_renderer_base.cc b/components/viz/service/display/delegated_ink_point_renderer_base.cc index 9918e52b..93ef86f 100644 --- a/components/viz/service/display/delegated_ink_point_renderer_base.cc +++ b/components/viz/service/display/delegated_ink_point_renderer_base.cc
@@ -6,17 +6,11 @@ #include "base/trace_event/trace_event.h" #include "components/viz/common/delegated_ink_metadata.h" -#include "ui/base/prediction/kalman_predictor.h" +#include "components/viz/service/display/delegated_ink_trail_data.h" namespace viz { -DelegatedInkPointRendererBase::DelegatedInkPointRendererBase() - : metrics_handler_("Renderer.DelegatedInkTrail.Prediction") { - unsigned int predictor_options = - ui::KalmanPredictor::PredictionOptions::kHeuristicsEnabled | - ui::KalmanPredictor::PredictionOptions::kDirectionCutOffEnabled; - predictor_ = std::make_unique<ui::KalmanPredictor>(predictor_options); -} +DelegatedInkPointRendererBase::DelegatedInkPointRendererBase() = default; DelegatedInkPointRendererBase::~DelegatedInkPointRendererBase() = default; void DelegatedInkPointRendererBase::InitMessagePipeline( @@ -29,7 +23,7 @@ if (receiver_.is_bound()) { receiver_.reset(); metadata_.reset(); - points_.clear(); + pointer_ids_.clear(); } receiver_.Bind(std::move(receiver)); } @@ -40,14 +34,42 @@ // at time of creation, so confirm that it was actually set. DCHECK_NE(metadata->frame_time(), base::TimeTicks()); metadata_ = std::move(metadata); + + // If we already have a cached pointer ID, check if the same pointer ID + // matches the new metadata. + if (pointer_id_.has_value() && + pointer_ids_[pointer_id_.value()].ContainsMatchingPoint( + metadata_.get())) { + return; + } + + // If not, find the pointer ID that does match it, if any, and cache it. + for (auto& it : pointer_ids_) { + if (it.second.ContainsMatchingPoint(metadata_.get())) { + pointer_id_ = it.first; + return; + } + } + + // If we aren't able to find any matching point, set the pointer ID to null + // so that FilterPoints and PredictPoints can early out. + pointer_id_ = base::nullopt; } std::vector<DelegatedInkPoint> DelegatedInkPointRendererBase::FilterPoints() { - if (points_.size() == 0) + if (pointer_ids_.size() == 0) return {}; DCHECK(metadata_); + // Any stored point with a timestamp earlier than the metadata's has already + // been drawn as part of the ink stroke and therefore should not be part of + // the delegated ink trail. Do this before checking if |pointer_id_| is valid + // because it helps manage the number of DelegatedInkPoints that are being + // stored and isn't dependent on |pointer_id_| at all. + for (auto& it : pointer_ids_) + it.second.ErasePointsOlderThanMetadata(metadata_.get()); + // TODO(1052145): Add additional filtering to prevent points in |points_| from // having a timestamp that is far ahead of |metadata_|'s timestamp. This could // occur if the renderer stalls before sending a metadata while the browser @@ -56,57 +78,33 @@ // stored here, resulting in a long possibly incorrect trail if the max // number of points to store was reached. - // First remove all points from |points_| with timestamps earlier than - // |metadata_|, as they have already been rendered by the app and are no - // longer useful for a trail. - // After that, there are three possible state of |points_|: - // 1. The earliest DelegatedInkPoint in |points_| matches |metadata_|'s - // timestamp. All the points in |points_| can be used to draw a trail. - // 2. |points_| is empty. No DelegatedInkPoints arrived from the browser - // process with a timestamp equal to or later than |metadata_|'s, so we - // don't have any points to make a trail from. - // 3. There are DelegatedInkPoints in |points_|, but the earliest one is - // later than |metadata_|. This can happen most often when the API is - // first used, as the browser process did not know to send the point - // to viz before it was used to make the metadata in the renderer. So - // although it didn't send the DelegatedInkPoint matching |metadata_|, it - // still may have sent future points before the metadata propagated all - // the way here. In this case, we choose not to use the points in - // |points_| to draw, as we have no way of confirming that there - // shouldn't be any extra points between |metadata_| and the beginning - // of |points_|. So instead, just leave everything after |metadata_| in - // |points_| so that they may be used in future trails and don't draw - // any trail for the current |metadata_|. - // So if |points_| contains a timestamp that matches |metadata_|'s timestamp, - // add it and every point after it to |points_to_draw| and return it for - // drawing. If it doesn't, just return an empty vector and leave any point - // with a timestamp later than |metadata_|'s in |points_|. - std::vector<DelegatedInkPoint> points_to_draw; + // If no point with any pointer id exactly matches the metadata, then we can't + // confirm which set of points to use for the delegated ink trail, so just + // return an empty vector so that nothing will be drawn. This happens most + // often at the beginning of delegated ink trail use. The metadata is created + // using a PointerEvent earlier than any DelegatedInkPoint is created, + // resulting in the metadata having an earlier timestamp and a point that + // doesn't match anything that is sent here from viz. Even if only a single + // pointer ID is in use, we can't know with any certainty what happened + // between the metadata point and the earliest DelegatedInkPoint we have, so + // we choose to just not draw anything. + if (!pointer_id_.has_value()) + return {}; - auto it = points_.begin(); - while (points_.size() > 0 && it != points_.end()) { - if (it->first < metadata_->timestamp()) { - // Sanity check to confirm that we always find the points that are before - // |metadata_|'s timestamp at the beginning of |points_| since it should - // be sorted. - DCHECK(it == points_.begin()); - it = points_.erase(it); - } else { - if (it->first == metadata_->timestamp() || points_to_draw.size() > 0) { - points_to_draw.push_back(it->second); - metrics_handler_.AddRealEvent(it->second.point(), - it->second.timestamp(), - metadata_->frame_time()); - it++; - } else { - // If we find a point that is later than |metadata_|'s timestamp before - // finding one that matches |metadata_|'s timestamp, that means that - // it doesn't exist in |points_|, so return an empty vector as there are - // no valid points to draw. - break; - } - } - } + DelegatedInkTrailData& trail_data = pointer_ids_[pointer_id_.value()]; + + // Make sure the metrics handler is provided the new real events to accurately + // measure the prediction later. + trail_data.UpdateMetrics(metadata_.get()); + + // Any remaining points must be the points that should be part of the + // delegated ink trail + std::vector<DelegatedInkPoint> points_to_draw; + for (auto it : trail_data.GetPoints()) + points_to_draw.emplace_back(it.second, it.first, pointer_id_.value()); + + DCHECK(points_to_draw.front().point() == metadata_->point() && + points_to_draw.front().timestamp() == metadata_->timestamp()); return points_to_draw; } @@ -114,26 +112,26 @@ void DelegatedInkPointRendererBase::PredictPoints( std::vector<DelegatedInkPoint>* ink_points_to_draw) { DCHECK(metadata_); + + if (!pointer_id_.has_value()) + return; + + DelegatedInkTrailData& trail_data = pointer_ids_[pointer_id_.value()]; int points_predicted = 0; // |ink_points_to_draw| needs to have at least one point in it already as a // reference to know what timestamp to start predicting points at. This single // point may just match |metadata_|. - if (predictor_->HasPrediction() && ink_points_to_draw->size() > 0) { + if (trail_data.HasPrediction() && ink_points_to_draw->size() > 0) { for (int i = 0; i < kNumberOfPointsToPredict; ++i) { base::TimeTicks timestamp = ink_points_to_draw->back().timestamp() + base::TimeDelta::FromMilliseconds( kNumberOfMillisecondsIntoFutureToPredictPerPoint); - std::unique_ptr<ui::InputPredictor::InputData> predicted_point = - predictor_->GeneratePrediction(timestamp); - if (predicted_point) { - ink_points_to_draw->emplace_back( - predicted_point->pos, predicted_point->time_stamp, - ink_points_to_draw->front().pointer_id()); - metrics_handler_.AddPredictedEvent(predicted_point->pos, - predicted_point->time_stamp, - metadata_->frame_time()); + base::Optional<DelegatedInkPoint> predicted_point = + trail_data.GetPredictedPoint(timestamp, metadata_->frame_time()); + if (predicted_point.has_value()) { + ink_points_to_draw->push_back(predicted_point.value()); points_predicted++; } else { // HasPrediction() can return true while GeneratePrediction() fails to @@ -149,12 +147,13 @@ TRACE_EVENT_SCOPE_THREAD, "predicted points", points_predicted); - metrics_handler_.EvaluatePrediction(); + if (points_predicted > 0) + trail_data.EvaluatePrediction(); } void DelegatedInkPointRendererBase::ResetPrediction() { - predictor_->Reset(); - metrics_handler_.Reset(); + for (auto& it : pointer_ids_) + it.second.Reset(); TRACE_EVENT_INSTANT0("viz", "Delegated ink prediction reset.", TRACE_EVENT_SCOPE_THREAD); } @@ -165,16 +164,7 @@ "DelegatedInkPointRendererImpl::StoreDelegatedInkPoint", TRACE_EVENT_SCOPE_THREAD, "point", point.ToString()); - predictor_->Update( - ui::InputPredictor::InputData(point.point(), point.timestamp())); - - // Fail-safe to prevent storing excessive points if they are being sent but - // never filtered and used, like if the renderer has stalled during a long - // running script. - if (points_.size() == kMaximumDelegatedInkPointsStored) - points_.erase(points_.begin()); - - points_.insert({point.timestamp(), point}); + pointer_ids_[point.pointer_id()].AddPoint(point); } } // namespace viz
diff --git a/components/viz/service/display/delegated_ink_point_renderer_base.h b/components/viz/service/display/delegated_ink_point_renderer_base.h index 0d72e6b..534996a 100644 --- a/components/viz/service/display/delegated_ink_point_renderer_base.h +++ b/components/viz/service/display/delegated_ink_point_renderer_base.h
@@ -5,24 +5,19 @@ #ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_DELEGATED_INK_POINT_RENDERER_BASE_H_ #define COMPONENTS_VIZ_SERVICE_DISPLAY_DELEGATED_INK_POINT_RENDERER_BASE_H_ -#include <map> #include <memory> +#include <unordered_map> #include <utility> #include <vector> +#include "base/optional.h" #include "components/viz/service/viz_service_export.h" #include "mojo/public/cpp/bindings/receiver.h" #include "services/viz/public/mojom/compositing/delegated_ink_point.mojom.h" -#include "ui/base/prediction/input_predictor.h" -#include "ui/base/prediction/prediction_metrics_handler.h" namespace viz { class DelegatedInkMetadata; - -// The maximum number of delegated ink points that will be stored at a time. -// When this is hit, the oldest one will be removed each time a new one is -// added. -constexpr int kMaximumDelegatedInkPointsStored = 10; +class DelegatedInkTrailData; // The number of points to predict into the future, when prediction is // available. @@ -73,9 +68,9 @@ private: friend class SkiaDelegatedInkRendererTest; - const std::map<base::TimeTicks, DelegatedInkPoint>& GetPointsMapForTest() - const { - return points_; + const std::unordered_map<int32_t, DelegatedInkTrailData>& + GetPointsMapForTest() const { + return pointer_ids_; } const DelegatedInkMetadata* GetMetadataForTest() const { @@ -84,16 +79,16 @@ virtual int GetPathPointCountForTest() const = 0; + // Cached pointer id that matches the most recent metadata. This is set when + // a metadata arrives, and if no stored DelegatedInkPoints match the metadata, + // then it is null. + base::Optional<int32_t> pointer_id_; + // The points that arrived from the browser process and may be drawn as part - // of the ink trail. - std::map<base::TimeTicks, DelegatedInkPoint> points_; - - // Kalman predictor that is used for generating predicted points. - std::unique_ptr<ui::InputPredictor> predictor_; - - // Handler for calculating useful metrics for evaluating predicted points - // and populating the histograms with those metrics. - ui::PredictionMetricsHandler metrics_handler_; + // of the ink trail are stored according to their pointer ids so that if + // more than one source of points is arriving, we can choose the correct set + // of points to use when drawing the delegated ink trail. + std::unordered_map<int32_t, DelegatedInkTrailData> pointer_ids_; mojo::Receiver<mojom::DelegatedInkPointRenderer> receiver_{this}; };
diff --git a/components/viz/service/display/delegated_ink_trail_data.cc b/components/viz/service/display/delegated_ink_trail_data.cc new file mode 100644 index 0000000..21cce34 --- /dev/null +++ b/components/viz/service/display/delegated_ink_trail_data.cc
@@ -0,0 +1,85 @@ +// 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 "components/viz/service/display/delegated_ink_trail_data.h" + +#include "components/viz/common/delegated_ink_metadata.h" +#include "components/viz/common/delegated_ink_point.h" +#include "ui/base/prediction/kalman_predictor.h" + +namespace viz { + +DelegatedInkTrailData::DelegatedInkTrailData() + : metrics_handler_("Renderer.DelegatedInkTrail.Prediction") { + unsigned int predictor_options = + ui::KalmanPredictor::PredictionOptions::kHeuristicsEnabled | + ui::KalmanPredictor::PredictionOptions::kDirectionCutOffEnabled; + predictor_ = std::make_unique<ui::KalmanPredictor>(predictor_options); +} + +DelegatedInkTrailData::~DelegatedInkTrailData() = default; + +void DelegatedInkTrailData::AddPoint(const DelegatedInkPoint& point) { + if (static_cast<int>(points_.size()) == 0) + pointer_id_ = point.pointer_id(); + else + DCHECK_EQ(pointer_id_, point.pointer_id()); + + predictor_->Update( + ui::InputPredictor::InputData(point.point(), point.timestamp())); + + // Fail-safe to prevent storing excessive points if they are being sent but + // never filtered and used, like if the renderer has stalled during a long + // running script. + if (points_.size() == kMaximumDelegatedInkPointsStored) + points_.erase(points_.begin()); + + points_.insert({point.timestamp(), point.point()}); +} + +base::Optional<DelegatedInkPoint> DelegatedInkTrailData::GetPredictedPoint( + base::TimeTicks timestamp, + base::TimeTicks frame_time) { + std::unique_ptr<ui::InputPredictor::InputData> predicted_point = + predictor_->GeneratePrediction(timestamp); + if (!predicted_point) + return base::nullopt; + + metrics_handler_.AddPredictedEvent(predicted_point->pos, + predicted_point->time_stamp, frame_time); + return DelegatedInkPoint(predicted_point->pos, predicted_point->time_stamp, + pointer_id_); +} + +void DelegatedInkTrailData::Reset() { + predictor_->Reset(); + metrics_handler_.Reset(); +} + +bool DelegatedInkTrailData::ContainsMatchingPoint( + DelegatedInkMetadata* metadata) const { + auto point = points_.find(metadata->timestamp()); + if (point == points_.end()) + return false; + + return point->second == metadata->point(); +} + +void DelegatedInkTrailData::ErasePointsOlderThanMetadata( + DelegatedInkMetadata* metadata) { + // Any points with a timestamp earlier than the metadata have already been + // drawn by the app. Since the metadata timestamp will only increase, we can + // safely erase every point earlier than it and be left only with the points + // that can be drawn. + while (points_.size() > 0 && points_.begin()->first < metadata->timestamp() && + points_.begin()->second != metadata->point()) + points_.erase(points_.begin()); +} + +void DelegatedInkTrailData::UpdateMetrics(DelegatedInkMetadata* metadata) { + for (auto it : points_) + metrics_handler_.AddRealEvent(it.second, it.first, metadata->frame_time()); +} + +} // namespace viz
diff --git a/components/viz/service/display/delegated_ink_trail_data.h b/components/viz/service/display/delegated_ink_trail_data.h new file mode 100644 index 0000000..1dbce08 --- /dev/null +++ b/components/viz/service/display/delegated_ink_trail_data.h
@@ -0,0 +1,65 @@ +// 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 COMPONENTS_VIZ_SERVICE_DISPLAY_DELEGATED_INK_TRAIL_DATA_H_ +#define COMPONENTS_VIZ_SERVICE_DISPLAY_DELEGATED_INK_TRAIL_DATA_H_ + +#include <map> +#include <memory> + +#include "base/optional.h" +#include "base/time/time.h" +#include "components/viz/service/viz_service_export.h" +#include "ui/base/prediction/input_predictor.h" +#include "ui/base/prediction/prediction_metrics_handler.h" +#include "ui/gfx/geometry/point_f.h" + +namespace viz { +class DelegatedInkMetadata; +class DelegatedInkPoint; + +// The maximum number of delegated ink points that will be stored at a time. +// When this is hit, the oldest one will be removed each time a new one is +// added. +constexpr int kMaximumDelegatedInkPointsStored = 10; + +class VIZ_SERVICE_EXPORT DelegatedInkTrailData { + public: + DelegatedInkTrailData(); + ~DelegatedInkTrailData(); + + void AddPoint(const DelegatedInkPoint& point); + base::Optional<DelegatedInkPoint> GetPredictedPoint( + base::TimeTicks timestamp, + base::TimeTicks frame_time); + void Reset(); + bool ContainsMatchingPoint(DelegatedInkMetadata* metadata) const; + void ErasePointsOlderThanMetadata(DelegatedInkMetadata* metadata); + void UpdateMetrics(DelegatedInkMetadata* metadata); + + const std::map<base::TimeTicks, gfx::PointF>& GetPoints() const { + return points_; + } + bool HasPrediction() const { return predictor_->HasPrediction(); } + void EvaluatePrediction() { metrics_handler_.EvaluatePrediction(); } + + private: + // The points that arrived from the browser process and will be used to draw + // the delegated ink trail. + std::map<base::TimeTicks, gfx::PointF> points_; + + // Kalman predictor that is used for generating predicted points. + std::unique_ptr<ui::InputPredictor> predictor_; + + // Handler for calculating useful metrics for evaluating predicted points + // and populating the histograms with those metrics. + ui::PredictionMetricsHandler metrics_handler_; + + // The pointer id associated with these points. + int32_t pointer_id_; +}; + +} // namespace viz + +#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_DELEGATED_INK_TRAIL_DATA_H_
diff --git a/components/viz/service/display/display_unittest.cc b/components/viz/service/display/display_unittest.cc index db1d0e6..950f8cd 100644 --- a/components/viz/service/display/display_unittest.cc +++ b/components/viz/service/display/display_unittest.cc
@@ -6,6 +6,7 @@ #include <limits> #include <map> +#include <unordered_map> #include <utility> #include "base/bind.h" @@ -32,6 +33,7 @@ #include "components/viz/common/surfaces/subtree_capture_id.h" #include "components/viz/service/display/aggregated_frame.h" #include "components/viz/service/display/delegated_ink_point_renderer_skia.h" +#include "components/viz/service/display/delegated_ink_trail_data.h" #include "components/viz/service/display/direct_renderer.h" #include "components/viz/service/display/display_client.h" #include "components/viz/service/display/display_scheduler.h" @@ -4536,19 +4538,51 @@ return display_->renderer_for_testing()->GetDelegatedInkPointRenderer(); } - const std::map<base::TimeTicks, DelegatedInkPoint>& stored_points() { - return ink_renderer()->GetPointsMapForTest(); + int UniqueStoredPointerIds() { + return ink_renderer()->GetPointsMapForTest().size(); + } + + int StoredPointsForPointerId(int32_t pointer_id) { + return GetPointsForPointerId(pointer_id).size(); + } + + const std::map<base::TimeTicks, gfx::PointF>& GetPointsForPointerId( + int32_t pointer_id) { + DCHECK(ink_renderer()->GetPointsMapForTest().find(pointer_id) != + ink_renderer()->GetPointsMapForTest().end()); + return ink_renderer() + ->GetPointsMapForTest() + .find(pointer_id) + ->second.GetPoints(); } void CreateAndStoreDelegatedInkPoint(const gfx::PointF& point, base::TimeTicks timestamp, int32_t pointer_id) { - ink_points_.emplace_back(point, timestamp, pointer_id); - ink_renderer()->StoreDelegatedInkPoint(ink_points_.back()); + ink_points_[pointer_id].emplace_back(point, timestamp, pointer_id); + ink_renderer()->StoreDelegatedInkPoint(ink_points_[pointer_id].back()); + } + + void CreateAndStoreDelegatedInkPointFromPreviousPoint(int32_t pointer_id) { + DCHECK(ink_points_.find(pointer_id) != ink_points_.end()); + + gfx::PointF point(ink_points_[pointer_id].back().point()); + point.Offset(10, 10); + + base::TimeTicks timestamp = ink_points_[pointer_id].back().timestamp(); + timestamp += base::TimeDelta::FromMilliseconds(5); + + CreateAndStoreDelegatedInkPoint(point, timestamp, pointer_id); } void StoreAlreadyCreatedDelegatedInkPoints() { - for (DelegatedInkPoint ink_point : ink_points_) + DCHECK_EQ(static_cast<int>(ink_points_.size()), 1); + StoreAlreadyCreatedDelegatedInkPoints(ink_points_.begin()->first); + } + + void StoreAlreadyCreatedDelegatedInkPoints(int32_t pointer_id) { + DCHECK(ink_points_.find(pointer_id) != ink_points_.end()); + for (DelegatedInkPoint ink_point : ink_points_[pointer_id]) ink_renderer()->StoreDelegatedInkPoint(ink_point); } @@ -4562,11 +4596,24 @@ float diameter, SkColor color, const gfx::RectF& presentation_area) { - EXPECT_GE(index, 0); - EXPECT_LT(index, ink_points_size()); + DCHECK_EQ(static_cast<int>(ink_points_.size()), 1); + return MakeAndSendMetadataFromStoredInkPoint( + ink_points_.begin()->first, index, diameter, color, presentation_area); + } - DelegatedInkMetadata metadata(ink_points_[index].point(), diameter, color, - ink_points_[index].timestamp(), + DelegatedInkMetadata MakeAndSendMetadataFromStoredInkPoint( + int32_t pointer_id, + int index, + float diameter, + SkColor color, + const gfx::RectF& presentation_area) { + DCHECK(ink_points_.find(pointer_id) != ink_points_.end()); + EXPECT_GE(index, 0); + EXPECT_LT(index, ink_points_size(pointer_id)); + + DelegatedInkMetadata metadata(ink_points_[pointer_id][index].point(), + diameter, color, + ink_points_[pointer_id][index].timestamp(), presentation_area, base::TimeTicks::Now(), /*hovering*/ false); SendMetadata(metadata); @@ -4621,15 +4668,34 @@ } const DelegatedInkPoint& ink_point(int index) { - EXPECT_GE(index, 0); - EXPECT_LT(index, ink_points_size()); - return ink_points_[index]; + DCHECK_EQ(static_cast<int>(ink_points_.size()), 1); + return ink_point(ink_points_.begin()->first, index); } - int ink_points_size() { return ink_points_.size(); } + const DelegatedInkPoint& ink_point(int32_t pointer_id, int index) { + DCHECK(ink_points_.find(pointer_id) != ink_points_.end()); + EXPECT_GE(index, 0); + EXPECT_LT(index, ink_points_size(pointer_id)); + return ink_points_[pointer_id][index]; + } + + const DelegatedInkPoint& last_ink_point(int32_t pointer_id) { + DCHECK(ink_points_.find(pointer_id) != ink_points_.end()); + return ink_points_[pointer_id].back(); + } + + int ink_points_size() { + DCHECK_EQ(static_cast<int>(ink_points_.size()), 1); + return ink_points_.begin()->second.size(); + } + + int ink_points_size(int32_t pointer_id) { + DCHECK(ink_points_.find(pointer_id) != ink_points_.end()); + return ink_points_[pointer_id].size(); + } private: - std::vector<DelegatedInkPoint> ink_points_; + std::unordered_map<int32_t, std::vector<DelegatedInkPoint>> ink_points_; }; // Testing filtering points in the the delegated ink renderer when the skia @@ -4638,7 +4704,7 @@ SetUpRenderers(); // First, a sanity check. - EXPECT_EQ(0, static_cast<int>(stored_points().size())); + EXPECT_EQ(0, UniqueStoredPointerIds()); // Insert 3 arbitrary points into the ink renderer to confirm that they go // where we expect and are all stored correctly. @@ -4646,20 +4712,22 @@ base::TimeTicks timestamp = base::TimeTicks::Now(); gfx::PointF point(10, 10); const int32_t kPointerId = std::numeric_limits<int32_t>::max(); - for (int i = 0; i < kInitialDelegatedPoints; ++i) { - CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId); - point.Offset(10, 10); - timestamp += base::TimeDelta::FromMilliseconds(5); - } + CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId); + for (int i = 1; i < kInitialDelegatedPoints; ++i) + CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerId); - EXPECT_EQ(kInitialDelegatedPoints, static_cast<int>(stored_points().size())); + // They all have the same pointer ID, so there should be exactly one unique + // element in the map, and that element should itself have all three points. + EXPECT_EQ(1, UniqueStoredPointerIds()); + EXPECT_EQ(kInitialDelegatedPoints, StoredPointsForPointerId(kPointerId)); // No metadata has been provided yet, so filtering shouldn't occur and all // points should still exist after a FinalizePath() call. FinalizePathAndCheckHistograms(base::TimeDelta::Min(), base::TimeDelta::Min()); - EXPECT_EQ(kInitialDelegatedPoints, static_cast<int>(stored_points().size())); + EXPECT_EQ(1, UniqueStoredPointerIds()); + EXPECT_EQ(kInitialDelegatedPoints, StoredPointsForPointerId(kPointerId)); // Now provide metadata with a timestamp matching one of the points to // confirm that earlier points are removed and later points remain. @@ -4670,18 +4738,19 @@ // The histogram should count one in the bucket that is the difference between // the latest point stored and the metadata. No prediction should occur with - // 3 provided, points, so the *WithPrediction histogram should count 1 in the + // 3 provided points, so the *WithPrediction histogram should count 1 in the // same bucket as the *WithoutPrediction histogram. base::TimeDelta bucket_without_prediction = - ink_point(ink_points_size() - 1).timestamp() - metadata.timestamp(); + last_ink_point(kPointerId).timestamp() - metadata.timestamp(); FinalizePathAndCheckHistograms(bucket_without_prediction, bucket_without_prediction); EXPECT_EQ(kInitialDelegatedPoints - kInkPointForMetadata, - static_cast<int>(stored_points().size())); - EXPECT_EQ(metadata.point(), stored_points().begin()->second.point()); - EXPECT_EQ(ink_point(ink_points_size() - 1).point(), - stored_points().rbegin()->second.point()); + StoredPointsForPointerId(kPointerId)); + EXPECT_EQ(metadata.point(), + GetPointsForPointerId(kPointerId).begin()->second); + EXPECT_EQ(last_ink_point(kPointerId).point(), + GetPointsForPointerId(kPointerId).rbegin()->second); EXPECT_EQ(ink_point(0).pointer_id(), kPointerId); // Confirm that the metadata is cleared when DrawDelegatedInkTrail() is @@ -4694,28 +4763,129 @@ const int kPointsBeyondMaxAllowed = 2; StoreAlreadyCreatedDelegatedInkPoints(); while (ink_points_size() < - kMaximumDelegatedInkPointsStored + kPointsBeyondMaxAllowed) { - CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId); - point.Offset(10, 10); - timestamp += base::TimeDelta::FromMilliseconds(10); - } + kMaximumDelegatedInkPointsStored + kPointsBeyondMaxAllowed) + CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerId); EXPECT_EQ(kMaximumDelegatedInkPointsStored, - static_cast<int>(stored_points().size())); + StoredPointsForPointerId(kPointerId)); EXPECT_EQ(ink_point(kPointsBeyondMaxAllowed).point(), - stored_points().begin()->second.point()); - EXPECT_EQ(ink_point(ink_points_size() - 1).point(), - stored_points().rbegin()->second.point()); - EXPECT_EQ(ink_point(ink_points_size() - 1).pointer_id(), kPointerId); + GetPointsForPointerId(kPointerId).begin()->second); + EXPECT_EQ(last_ink_point(kPointerId).point(), + GetPointsForPointerId(kPointerId).rbegin()->second); + EXPECT_EQ(last_ink_point(kPointerId).pointer_id(), kPointerId); // Now send metadata with a timestamp before all of the points that are // currently stored to confirm that no points are filtered out and the number // stored remains the same while both histograms records 0 improvement. - const uint64_t kExpectedPoints = stored_points().size(); + const int kExpectedPoints = StoredPointsForPointerId(kPointerId); SendMetadata(metadata); FinalizePathAndCheckHistograms(base::TimeDelta::FromMilliseconds(0), base::TimeDelta::FromMilliseconds(0)); - EXPECT_EQ(kExpectedPoints, stored_points().size()); + EXPECT_EQ(kExpectedPoints, StoredPointsForPointerId(kPointerId)); +} + +// Test filtering when points arrive with several different pointer IDs. +TEST_F(SkiaDelegatedInkRendererTest, + SkiaDelegatedInkRendererFilteringPointsWithMultiplePointerIds) { + SetUpRenderers(); + + // Unique pointer IDs used - numbers arbitrary. + const std::vector<int32_t> kPointerIds = {1, 20, 300}; + + // First add just one DelegatedInkPoint for each pointer id to confirm that + // they all get stored separately. + base::TimeTicks timestamp = base::TimeTicks::Now(); + for (uint64_t i = 0; i < kPointerIds.size(); ++i) { + // Make sure that each pointer id has slightly different points so that when + // new points are added later that are based on previous points, it doesn't + // result in multiple pointer ids having identical DelegatedInkPoints + CreateAndStoreDelegatedInkPoint(gfx::PointF(i * 5, i * 10), timestamp, + kPointerIds[i]); + timestamp += base::TimeDelta::FromMilliseconds(5); + } + + EXPECT_EQ(static_cast<int>(kPointerIds.size()), UniqueStoredPointerIds()); + for (int32_t pointer_id : kPointerIds) + EXPECT_EQ(1, StoredPointsForPointerId(pointer_id)); + + // Add more points so that the first pointer ID contains 4 DelegatedInkPoints, + // and the third pointer id contains 2 DelegatedInkPoints + const int kNumPointsForPointerId0 = 4; + while (ink_points_size(kPointerIds[0]) < kNumPointsForPointerId0) + CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerIds[0]); + CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerIds[2]); + + // Confirm all the points got stored where they should have been. + for (int32_t pointer_id : kPointerIds) { + EXPECT_EQ(ink_points_size(pointer_id), + StoredPointsForPointerId(pointer_id)); + } + + // Now provide metadata with a timestamp matching one of the points in the + // first pointer id bucket to confirm that earlier points are removed and + // later points remain. + const int kInkPointForMetadata = 1; + const float kDiameter = 1.f; + DelegatedInkMetadata metadata = MakeAndSendMetadataFromStoredInkPoint( + kPointerIds[0], kInkPointForMetadata, kDiameter, SK_ColorBLACK, + gfx::RectF()); + + // 3 points should be enough for prediction to work, so the histogram should + // have one in the *WithoutPrediction bucket that matches the difference + // between the metadata and the final point, and one in the *WithPrediction + // bucket that matches the amount of prediction that is being done (plus the + // difference between the final point and the metadata). + base::TimeDelta bucket_without_prediction = + last_ink_point(kPointerIds[0]).timestamp() - metadata.timestamp(); + FinalizePathAndCheckHistograms( + bucket_without_prediction, + bucket_without_prediction + + base::TimeDelta::FromMilliseconds( + kNumberOfMillisecondsIntoFutureToPredictPerPoint * + kNumberOfPointsToPredict)); + + // Confirm the size, first, and last points of the first pointer ID are what + // we expect. + EXPECT_EQ(kNumPointsForPointerId0 - kInkPointForMetadata, + StoredPointsForPointerId(kPointerIds[0])); + EXPECT_EQ(metadata.point(), + GetPointsForPointerId(kPointerIds[0]).begin()->second); + EXPECT_EQ(last_ink_point(kPointerIds[0]).point(), + GetPointsForPointerId(kPointerIds[0]).rbegin()->second); + + // Confirm that neither of the other pointer ids were impacted. + for (uint64_t i = 1; i < kPointerIds.size(); ++i) { + EXPECT_EQ(ink_points_size(kPointerIds[i]), + StoredPointsForPointerId(kPointerIds[i])); + } + + // Send a metadata whose point and timestamp doesn't match any stored + // DelegatedInkPoint and confirm that it doesn't cause any changes to the + // stored values. Histograms should have 1 in both 0 buckets since no points + // will be drawn. + SendMetadata(DelegatedInkMetadata( + gfx::PointF(100, 100), 5.6f, SK_ColorBLACK, base::TimeTicks::Min(), + gfx::RectF(), base::TimeTicks::Min(), /*hovering*/ false)); + FinalizePathAndCheckHistograms(base::TimeDelta::FromMilliseconds(0), + base::TimeDelta::FromMilliseconds(0)); + EXPECT_EQ(kNumPointsForPointerId0 - kInkPointForMetadata, + StoredPointsForPointerId(kPointerIds[0])); + for (uint64_t i = 1; i < kPointerIds.size(); ++i) { + EXPECT_EQ(ink_points_size(kPointerIds[i]), + StoredPointsForPointerId(kPointerIds[i])); + } + + // Finally, send a metadata with a timestamp beyond all of the stored points. + // This should result in all of the points being erased, but the pointer ids + // will still exist as they contains the predictors as well. + SendMetadata(DelegatedInkMetadata( + gfx::PointF(100, 100), 5.6f, SK_ColorBLACK, + base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(1000), + gfx::RectF(), base::TimeTicks::Now(), /*hovering*/ false)); + FinalizePathAndCheckHistograms(base::TimeDelta::FromMilliseconds(0), + base::TimeDelta::FromMilliseconds(0)); + for (int i : kPointerIds) + EXPECT_EQ(0, StoredPointsForPointerId(i)); } // Confirm that the delegated ink trail histograms record latency correctly.
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc index 4903b9c..d8b5617 100644 --- a/components/viz/service/display/renderer_pixeltest.cc +++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -5226,17 +5226,19 @@ // Draw a single trail and erase it, making sure that no bits of trail are left // behind. TEST_P(DelegatedInkTest, DrawOneTrailAndErase) { - // First provide the metadata required to draw the trail, numbers arbitrary. - CreateAndSendMetadata(gfx::PointF(10, 10), 3.5f, SK_ColorBLACK, - gfx::RectF(0, 0, 175, 172)); - - // Then provide some points for the trail to draw. Numbers chosen arbitrarily - // after the first point, which must match the metadata. This will predict no + // Send some DelegatedInkPoints, numbers arbitrary. This will predict no // points, so a trail made of 3 points will be drawn. - CreateAndSendPointFromMetadata(); + const gfx::PointF kFirstPoint(10, 10); + const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now(); + CreateAndSendPoint(kFirstPoint, kFirstTimestamp); CreateAndSendPointFromLastPoint(gfx::PointF(75, 62)); CreateAndSendPointFromLastPoint(gfx::PointF(124, 45)); + // Provide the metadata required to draw the trail, matching the first + // DelegatedInkPoint sent. + CreateAndSendMetadata(kFirstPoint, 3.5f, SK_ColorBLACK, kFirstTimestamp, + gfx::RectF(0, 0, 175, 172)); + // Confirm that the trail was drawn. EXPECT_TRUE( DrawAndTestTrail(FILE_PATH_LITERAL("delegated_ink_one_trail.png"))); @@ -5252,16 +5254,18 @@ if (renderer_type() == RendererType::kSkiaDawn) return; - // First provide the metadata required to draw the trail, numbers arbitrary. - CreateAndSendMetadata(gfx::PointF(140, 48), 8.2f, SK_ColorMAGENTA, - gfx::RectF(0, 0, 200, 200)); - - // Then provide some points for the trail to draw. Numbers chosen arbitrarily - // after the first point, which must match the metadata. No points will be - // predicted, so a trail made of 2 points will be drawn. - CreateAndSendPointFromMetadata(); + // Numbers chosen arbitrarily. No points will be predicted, so a trail made of + // 2 points will be drawn. + const gfx::PointF kFirstPoint(140, 48); + const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now(); + CreateAndSendPoint(kFirstPoint, kFirstTimestamp); CreateAndSendPointFromLastPoint(gfx::PointF(115, 85)); + // Provide the metadata required to draw the trail, numbers matching the first + // DelegatedInkPoint sent. + CreateAndSendMetadata(kFirstPoint, 8.2f, SK_ColorMAGENTA, kFirstTimestamp, + gfx::RectF(0, 0, 200, 200)); + // Confirm that the trail was drawn correctly. EXPECT_TRUE(DrawAndTestTrail( FILE_PATH_LITERAL("delegated_ink_two_trails_first.png"))); @@ -5287,14 +5291,13 @@ if (renderer_type() == RendererType::kSkiaDawn) return; - const gfx::RectF kPresentationArea(30, 30, 100, 100); - CreateAndSendMetadata(gfx::PointF(50.2f, 89.999f), 15.22f, SK_ColorCYAN, - kPresentationArea); + const gfx::PointF kFirstPoint(50.2f, 89.999f); + const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now(); // Send points such that some extend beyond the presentation area to confirm // that the trail is clipped correctly. One point will be predicted, so the // trail will be made of 9 points. - CreateAndSendPointFromMetadata(); + CreateAndSendPoint(kFirstPoint, kFirstTimestamp); CreateAndSendPointFromLastPoint(gfx::PointF(80.7f, 149.6f)); CreateAndSendPointFromLastPoint(gfx::PointF(128.999f, 110.01f)); CreateAndSendPointFromLastPoint(gfx::PointF(50, 50)); @@ -5302,6 +5305,11 @@ CreateAndSendPointFromLastPoint(gfx::PointF(29.98f, 66)); CreateAndSendPointFromLastPoint(gfx::PointF(52.3456f, 2.31f)); CreateAndSendPointFromLastPoint(gfx::PointF(97, 36.9f)); + + const gfx::RectF kPresentationArea(30, 30, 100, 100); + CreateAndSendMetadata(kFirstPoint, 15.22f, SK_ColorCYAN, kFirstTimestamp, + kPresentationArea); + EXPECT_TRUE(DrawAndTestTrail(FILE_PATH_LITERAL( "delegated_ink_trail_clipped_by_presentation_area.png"))); } @@ -5332,13 +5340,16 @@ AggregatedRenderPassList pass_list; pass_list.push_back(std::move(pass)); - const gfx::RectF kPresentationArea(0, 0, 200, 200); - CreateAndSendMetadata(gfx::PointF(34.f, 72.f), 7.77f, SK_ColorDKGRAY, - kPresentationArea); - CreateAndSendPointFromMetadata(); + const gfx::PointF kFirstPoint(34.f, 72.f); + const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now(); + CreateAndSendPoint(kFirstPoint, kFirstTimestamp); CreateAndSendPointFromLastPoint(gfx::PointF(79, 101)); CreateAndSendPointFromLastPoint(gfx::PointF(134, 114)); + const gfx::RectF kPresentationArea(0, 0, 200, 200); + CreateAndSendMetadata(kFirstPoint, 7.77f, SK_ColorDKGRAY, kFirstTimestamp, + kPresentationArea); + EXPECT_TRUE(this->RunPixelTest( &pass_list, base::FilePath( @@ -5375,13 +5386,16 @@ pass_list.push_back(std::move(root_pass)); // Values for a simple delegated ink trail, numbers chosen arbitrarily. - const gfx::RectF kPresentationArea(0, 0, 200, 200); - CreateAndSendMetadata(gfx::PointF(156.f, 111.f), 19.177f, SK_ColorRED, - kPresentationArea); - CreateAndSendPointFromMetadata(); + const gfx::PointF kFirstPoint(156.f, 111.f); + const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now(); + CreateAndSendPoint(kFirstPoint, kFirstTimestamp); CreateAndSendPointFromLastPoint(gfx::PointF(119, 87.23f)); CreateAndSendPointFromLastPoint(gfx::PointF(74.222f, 95.4f)); + const gfx::RectF kPresentationArea(0, 0, 200, 200); + CreateAndSendMetadata(kFirstPoint, 19.177f, SK_ColorRED, kFirstTimestamp, + kPresentationArea); + // This will only check what was drawn in the child pass, which should never // contain a delegated ink trail, so it should be solid green. EXPECT_TRUE(this->RunPixelTestWithReadbackTarget( @@ -5389,6 +5403,62 @@ base::FilePath(FILE_PATH_LITERAL("green.png")), cc::ExactPixelComparator(true))); } + +// Draw two different trails that are made up of sets of DelegatedInkPoints with +// different pointer IDs. All numbers arbitrarily chosen. +TEST_P(DelegatedInkTest, DrawTrailsWithDifferentPointerIds) { + const int32_t kPointerId1 = 2; + const int32_t kPointerId2 = 100; + + const base::TimeTicks kTimestamp = base::TimeTicks::Now(); + + // Constants used for sending points and making sure we can send matching + // DelegatedInkMetadata later. + const gfx::PointF kPointerId1StartPoint(40, 27); + const base::TimeTicks kPointerId1StartTime = kTimestamp; + const gfx::PointF kPointerId2StartPoint(160, 190); + const base::TimeTicks kPointerId2StartTime = + kTimestamp + base::TimeDelta::FromMilliseconds(15); + + // Send four points for pointer ID 1 and two points for pointer ID 2 in mixed + // order to confirm that they get put in the right buckets. Some timestamps + // match intentionally to make sure that point is considered when matching + // DelegatedInkMetadata to DelegatedInkPoints + CreateAndSendPoint(kPointerId1StartPoint, kPointerId1StartTime, kPointerId1); + CreateAndSendPoint(gfx::PointF(24, 80), + kTimestamp + base::TimeDelta::FromMilliseconds(15), + kPointerId1); + CreateAndSendPoint(kPointerId2StartPoint, kPointerId2StartTime, kPointerId2); + CreateAndSendPoint(gfx::PointF(60, 130), + kTimestamp + base::TimeDelta::FromMilliseconds(24), + kPointerId1); + CreateAndSendPoint(gfx::PointF(80, 118), + kTimestamp + base::TimeDelta::FromMilliseconds(20), + kPointerId2); + CreateAndSendPoint(gfx::PointF(100, 190), + kTimestamp + base::TimeDelta::FromMilliseconds(30), + kPointerId1); + + const gfx::RectF kPresentationArea(200, 200); + + // Now send a metadata to match the first point of the first pointer id to + // confirm that only that trail is drawn. + CreateAndSendMetadata(kPointerId1StartPoint, 7, SK_ColorYELLOW, + kPointerId1StartTime, kPresentationArea); + EXPECT_TRUE( + DrawAndTestTrail(FILE_PATH_LITERAL("delegated_ink_pointer_id_1.png"))); + + // Then send metadata that matches the first point of the other pointer id. + // These points should not have been erased, so all 3 points should be drawn. + CreateAndSendMetadata(kPointerId2StartPoint, 2.4f, SK_ColorRED, + kPointerId2StartTime, kPresentationArea); + EXPECT_TRUE( + DrawAndTestTrail(FILE_PATH_LITERAL("delegated_ink_pointer_id_2.png"))); + + // The metadata should have been cleared after drawing, so confirm that there + // is no trail after another draw. + EXPECT_TRUE(DrawAndTestTrail(FILE_PATH_LITERAL("white.png"))); +} #endif // !defined(OS_ANDROID) } // namespace
diff --git a/components/viz/service/display/surface_aggregator_pixeltest.cc b/components/viz/service/display/surface_aggregator_pixeltest.cc index 96203ed4..7789e7db 100644 --- a/components/viz/service/display/surface_aggregator_pixeltest.cc +++ b/components/viz/service/display/surface_aggregator_pixeltest.cc
@@ -347,12 +347,16 @@ // Create and send metadata and points to the renderer that will be drawn. // Points and timestamps are chosen arbitrarily. - delegated_ink_helper.CreateAndSendMetadata( - gfx::PointF(10, 10), 7.7f, SK_ColorWHITE, gfx::RectF(0, 0, 200, 200)); - delegated_ink_helper.CreateAndSendPointFromMetadata(); + const gfx::PointF kFirstPoint(10, 10); + const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now(); + delegated_ink_helper.CreateAndSendPoint(kFirstPoint, kFirstTimestamp); delegated_ink_helper.CreateAndSendPointFromLastPoint(gfx::PointF(26, 37)); delegated_ink_helper.CreateAndSendPointFromLastPoint(gfx::PointF(45, 87)); + delegated_ink_helper.CreateAndSendMetadata(kFirstPoint, 7.7f, SK_ColorWHITE, + kFirstTimestamp, + gfx::RectF(0, 0, 200, 200)); + gfx::Rect rect(this->device_viewport_size_); CompositorRenderPassId id{1}; auto pass = CompositorRenderPass::Create();
diff --git a/components/viz/test/data/delegated_ink_pointer_id_1.png b/components/viz/test/data/delegated_ink_pointer_id_1.png new file mode 100644 index 0000000..516f0d1 --- /dev/null +++ b/components/viz/test/data/delegated_ink_pointer_id_1.png Binary files differ
diff --git a/components/viz/test/data/delegated_ink_pointer_id_2.png b/components/viz/test/data/delegated_ink_pointer_id_2.png new file mode 100644 index 0000000..69ae4a1 --- /dev/null +++ b/components/viz/test/data/delegated_ink_pointer_id_2.png Binary files differ
diff --git a/components/webapps/browser/banners/app_banner_manager.cc b/components/webapps/browser/banners/app_banner_manager.cc index 3b51c2d..4f8f6e2 100644 --- a/components/webapps/browser/banners/app_banner_manager.cc +++ b/components/webapps/browser/banners/app_banner_manager.cc
@@ -28,6 +28,7 @@ #include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" +#include "content/public/common/url_utils.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/remote.h" #include "services/service_manager/public/cpp/interface_provider.h" @@ -233,6 +234,20 @@ AppBannerManager::~AppBannerManager() = default; +bool AppBannerManager::ShouldIgnore(content::RenderFrameHost* render_frame_host, + const GURL& url) { + // Don't start the banner flow unless the main frame has finished loading. + // |render_frame_host| can be null during retry attempts. + if (render_frame_host && render_frame_host->GetParent()) + return true; + + // There is never a need to trigger a banner for a WebUI page. + if (content::HasWebUIScheme(url)) + return true; + + return false; +} + bool AppBannerManager::CheckIfShouldShowBanner() { if (ShouldBypassEngagementChecks()) return true; @@ -591,9 +606,7 @@ void AppBannerManager::DidFinishLoad( content::RenderFrameHost* render_frame_host, const GURL& validated_url) { - // Don't start the banner flow unless the main frame has finished loading. - // |render_frame_host| can be null during retry attempts. - if (render_frame_host && render_frame_host->GetParent()) + if (ShouldIgnore(render_frame_host, validated_url)) return; load_finished_ = true;
diff --git a/components/webapps/browser/banners/app_banner_manager.h b/components/webapps/browser/banners/app_banner_manager.h index c4ae1e3..7a682aa 100644 --- a/components/webapps/browser/banners/app_banner_manager.h +++ b/components/webapps/browser/banners/app_banner_manager.h
@@ -200,6 +200,11 @@ explicit AppBannerManager(content::WebContents* web_contents); ~AppBannerManager() override; + // Returns true if |render_frame_host| and |url| should be ignored and not + // trigger the banner flow. + bool ShouldIgnore(content::RenderFrameHost* render_frame_host, + const GURL& url); + // Returns true if the banner should be shown. Returns false if the banner has // been shown too recently, or if the app has already been installed. // GetAppIdentifier() must return a valid value for this method to work.
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn index 4f33afdc..1bd4d49 100644 --- a/content/browser/BUILD.gn +++ b/content/browser/BUILD.gn
@@ -486,8 +486,6 @@ "blob_storage/blob_internals_url_loader.h", "blob_storage/blob_registry_wrapper.cc", "blob_storage/blob_registry_wrapper.h", - "blob_storage/blob_storage_context_wrapper.cc", - "blob_storage/blob_storage_context_wrapper.h", "blob_storage/chrome_blob_storage_context.cc", "blob_storage/chrome_blob_storage_context.h", "bluetooth/bluetooth_adapter_factory_wrapper.cc", @@ -552,6 +550,8 @@ "browsing_instance.h", "byte_stream.cc", "byte_stream.h", + "cache_storage/blob_storage_context_wrapper.cc", + "cache_storage/blob_storage_context_wrapper.h", "cache_storage/cache_storage.cc", "cache_storage/cache_storage.h", "cache_storage/cache_storage_blob_to_disk_cache.cc", @@ -1938,6 +1938,7 @@ sources += [ "../app_shim_remote_cocoa/web_drag_source_mac.h", "../app_shim_remote_cocoa/web_drag_source_mac.mm", + "accessibility/accessibility_event_recorder_mac.h", "accessibility/accessibility_event_recorder_mac.mm", "accessibility/accessibility_tools_utils_mac.h", "accessibility/accessibility_tools_utils_mac.mm", @@ -2439,6 +2440,7 @@ if (use_atk) { sources += [ "accessibility/accessibility_event_recorder_auralinux.cc", + "accessibility/accessibility_event_recorder_auralinux.h", "accessibility/accessibility_tree_formatter_auralinux.cc", "accessibility/accessibility_tree_formatter_auralinux.h", "accessibility/accessibility_tree_formatter_utils_auralinux.cc",
diff --git a/content/browser/accessibility/accessibility_event_recorder_auralinux.cc b/content/browser/accessibility/accessibility_event_recorder_auralinux.cc index 4242bf5..da72155b 100644 --- a/content/browser/accessibility/accessibility_event_recorder_auralinux.cc +++ b/content/browser/accessibility/accessibility_event_recorder_auralinux.cc
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/browser/accessibility/accessibility_event_recorder.h" +#include "content/browser/accessibility/accessibility_event_recorder_auralinux.h" #include <atk/atk.h> #include <atk/atkutil.h> @@ -22,52 +22,6 @@ namespace content { -// This class has two distinct event recording code paths. When we are -// recording events in-process (typically this is used for -// DumpAccessibilityEvents tests), we use ATK's global event handlers. Since -// ATK doesn't support intercepting events from other processes, if we have a -// non-zero PID or an accessibility application name pattern, we use AT-SPI2 -// directly to intercept events. Since AT-SPI2 should be capable of -// intercepting events in-process as well, eventually it would be nice to -// remove the ATK code path entirely. -class AccessibilityEventRecorderAuraLinux : public AccessibilityEventRecorder { - public: - explicit AccessibilityEventRecorderAuraLinux( - BrowserAccessibilityManager* manager, - base::ProcessId pid, - const AXTreeSelector& selector); - ~AccessibilityEventRecorderAuraLinux() override; - - void ProcessATKEvent(const char* event, - unsigned int n_params, - const GValue* params); - void ProcessATSPIEvent(const AtspiEvent* event); - - static gboolean OnATKEventReceived(GSignalInvocationHint* hint, - unsigned int n_params, - const GValue* params, - gpointer data); - - private: - bool ShouldUseATSPI(); - - std::string AtkObjectToString(AtkObject* obj, bool include_name); - void AddATKEventListener(const char* event_name); - void AddATKEventListeners(); - void RemoveATKEventListeners(); - bool IncludeState(AtkStateType state_type); - - void AddATSPIEventListeners(); - void RemoveATSPIEventListeners(); - - AtspiEventListener* atspi_event_listener_ = nullptr; - base::ProcessId pid_; - base::StringPiece application_name_match_pattern_; - static AccessibilityEventRecorderAuraLinux* instance_; - - DISALLOW_COPY_AND_ASSIGN(AccessibilityEventRecorderAuraLinux); -}; - // static AccessibilityEventRecorderAuraLinux* AccessibilityEventRecorderAuraLinux::instance_ = nullptr;
diff --git a/content/browser/accessibility/accessibility_event_recorder_auralinux.h b/content/browser/accessibility/accessibility_event_recorder_auralinux.h new file mode 100644 index 0000000..e8db8d0 --- /dev/null +++ b/content/browser/accessibility/accessibility_event_recorder_auralinux.h
@@ -0,0 +1,66 @@ +// 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 CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_EVENT_RECORDER_AURALINUX_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_EVENT_RECORDER_AURALINUX_H_ + +#include <atk/atk.h> +#include <atspi/atspi.h> + +#include "content/browser/accessibility/accessibility_event_recorder.h" +#include "content/common/content_export.h" + +namespace content { + +// This class has two distinct event recording code paths. When we are +// recording events in-process (typically this is used for +// DumpAccessibilityEvents tests), we use ATK's global event handlers. Since +// ATK doesn't support intercepting events from other processes, if we have a +// non-zero PID or an accessibility application name pattern, we use AT-SPI2 +// directly to intercept events. +// TODO(crbug.com/1133330) AT-SPI2 should be capable of intercepting events +// in-process as well, thus it should be possible to remove the ATK code path +// entirely. +class CONTENT_EXPORT AccessibilityEventRecorderAuraLinux + : public AccessibilityEventRecorder { + public: + explicit AccessibilityEventRecorderAuraLinux( + BrowserAccessibilityManager* manager, + base::ProcessId pid, + const AXTreeSelector& selector); + ~AccessibilityEventRecorderAuraLinux() override; + + void ProcessATKEvent(const char* event, + unsigned int n_params, + const GValue* params); + void ProcessATSPIEvent(const AtspiEvent* event); + + static gboolean OnATKEventReceived(GSignalInvocationHint* hint, + unsigned int n_params, + const GValue* params, + gpointer data); + + private: + bool ShouldUseATSPI(); + + std::string AtkObjectToString(AtkObject* obj, bool include_name); + void AddATKEventListener(const char* event_name); + void AddATKEventListeners(); + void RemoveATKEventListeners(); + bool IncludeState(AtkStateType state_type); + + void AddATSPIEventListeners(); + void RemoveATSPIEventListeners(); + + AtspiEventListener* atspi_event_listener_ = nullptr; + base::ProcessId pid_; + base::StringPiece application_name_match_pattern_; + static AccessibilityEventRecorderAuraLinux* instance_; + + DISALLOW_COPY_AND_ASSIGN(AccessibilityEventRecorderAuraLinux); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_EVENT_RECORDER_AURALINUX_H_
diff --git a/content/browser/accessibility/accessibility_event_recorder_mac.h b/content/browser/accessibility/accessibility_event_recorder_mac.h new file mode 100644 index 0000000..d05178d3 --- /dev/null +++ b/content/browser/accessibility/accessibility_event_recorder_mac.h
@@ -0,0 +1,54 @@ +// 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 CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_EVENT_RECORDER_MAC_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_EVENT_RECORDER_MAC_H_ + +#include "content/browser/accessibility/accessibility_event_recorder.h" +#include "content/browser/accessibility/browser_accessibility_cocoa.h" + +@class BrowserAccessibilityCocoa; + +namespace content { + +// Implementation of AccessibilityEventRecorder that uses AXObserver to +// watch for NSAccessibility events. +class CONTENT_EXPORT AccessibilityEventRecorderMac + : public AccessibilityEventRecorder { + public: + AccessibilityEventRecorderMac(BrowserAccessibilityManager* manager, + base::ProcessId pid, + AXUIElementRef node); + ~AccessibilityEventRecorderMac() override; + + // Callback executed every time we receive an event notification. + void EventReceived(AXUIElementRef element, + CFStringRef notification, + CFDictionaryRef user_info); + static std::string SerializeTextSelectionChangedProperties( + CFDictionaryRef user_info); + + private: + // Add one notification to the list of notifications monitored by our + // observer. + void AddNotification(NSString* notification); + + // Convenience function to get the value of an AX attribute from + // an AXUIElementRef as a string. + std::string GetAXAttributeValue(AXUIElementRef element, + NSString* attribute_name); + + // The AXUIElement for the Chrome application. + base::ScopedCFTypeRef<AXUIElementRef> application_; + + // The AXObserver we use to monitor AX notifications. + base::ScopedCFTypeRef<AXObserverRef> observer_ref_; + CFRunLoopSourceRef observer_run_loop_source_; + + DISALLOW_COPY_AND_ASSIGN(AccessibilityEventRecorderMac); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_EVENT_RECORDER_MAC_H_
diff --git a/content/browser/accessibility/accessibility_event_recorder_mac.mm b/content/browser/accessibility/accessibility_event_recorder_mac.mm index cdf4f8f8..e065a8a77 100644 --- a/content/browser/accessibility/accessibility_event_recorder_mac.mm +++ b/content/browser/accessibility/accessibility_event_recorder_mac.mm
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/browser/accessibility/accessibility_event_recorder.h" +#include "content/browser/accessibility/accessibility_event_recorder_mac.h" #import <Cocoa/Cocoa.h> @@ -20,42 +20,6 @@ namespace content { -// Implementation of AccessibilityEventRecorder that uses AXObserver to -// watch for NSAccessibility events. -class AccessibilityEventRecorderMac : public AccessibilityEventRecorder { - public: - AccessibilityEventRecorderMac(BrowserAccessibilityManager* manager, - base::ProcessId pid, - AXUIElementRef node); - ~AccessibilityEventRecorderMac() override; - - // Callback executed every time we receive an event notification. - void EventReceived(AXUIElementRef element, - CFStringRef notification, - CFDictionaryRef user_info); - static std::string SerializeTextSelectionChangedProperties( - CFDictionaryRef user_info); - - private: - // Add one notification to the list of notifications monitored by our - // observer. - void AddNotification(NSString* notification); - - // Convenience function to get the value of an AX attribute from - // an AXUIElementRef as a string. - std::string GetAXAttributeValue(AXUIElementRef element, - NSString* attribute_name); - - // The AXUIElement for the Chrome application. - base::ScopedCFTypeRef<AXUIElementRef> application_; - - // The AXObserver we use to monitor AX notifications. - base::ScopedCFTypeRef<AXObserverRef> observer_ref_; - CFRunLoopSourceRef observer_run_loop_source_; - - DISALLOW_COPY_AND_ASSIGN(AccessibilityEventRecorderMac); -}; - // Callback function registered using AXObserverCreate. static void EventReceivedThunk(AXObserverRef observer_ref, AXUIElementRef element,
diff --git a/content/browser/accessibility/browser_accessibility_state_impl_win.cc b/content/browser/accessibility/browser_accessibility_state_impl_win.cc index fffcddd9..cb53197 100644 --- a/content/browser/accessibility/browser_accessibility_state_impl_win.cc +++ b/content/browser/accessibility/browser_accessibility_state_impl_win.cc
@@ -162,7 +162,7 @@ for (size_t i = 0; i < module_count; i++) { TCHAR filename[MAX_PATH]; GetModuleFileName(modules[i], filename, base::size(filename)); - base::string16 module_name(base::FilePath(filename).BaseName().value()); + std::string module_name(base::FilePath(filename).BaseName().AsUTF8Unsafe()); if (base::LowerCaseEqualsASCII(module_name, "fsdomsrv.dll")) g_jaws = true; if (base::LowerCaseEqualsASCII(module_name, "vbufbackend_gecko_ia2.dll") ||
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h index e094f70..2a0591d 100644 --- a/content/browser/bad_message.h +++ b/content/browser/bad_message.h
@@ -266,6 +266,7 @@ CSDH_BAD_OWNER = 238, SYNC_COMPOSITOR_NO_LOCAL_SURFACE_ID = 239, WCI_INVALID_FULLSCREEN_OPTIONS = 240, + PAYMENTS_WITHOUT_PERMISSION = 241, // Please add new elements here. The naming convention is abbreviated class // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/blob_storage/blob_storage_context_wrapper.cc b/content/browser/cache_storage/blob_storage_context_wrapper.cc similarity index 81% rename from content/browser/blob_storage/blob_storage_context_wrapper.cc rename to content/browser/cache_storage/blob_storage_context_wrapper.cc index a0c57cbb..dfc4a9f7 100644 --- a/content/browser/blob_storage/blob_storage_context_wrapper.cc +++ b/content/browser/cache_storage/blob_storage_context_wrapper.cc
@@ -2,10 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/browser/blob_storage/blob_storage_context_wrapper.h" - -#include "base/task/post_task.h" -#include "content/public/browser/browser_task_traits.h" +#include "content/browser/cache_storage/blob_storage_context_wrapper.h" namespace content {
diff --git a/content/browser/blob_storage/blob_storage_context_wrapper.h b/content/browser/cache_storage/blob_storage_context_wrapper.h similarity index 83% rename from content/browser/blob_storage/blob_storage_context_wrapper.h rename to content/browser/cache_storage/blob_storage_context_wrapper.h index 33e2117..c4ef0ec 100644 --- a/content/browser/blob_storage/blob_storage_context_wrapper.h +++ b/content/browser/cache_storage/blob_storage_context_wrapper.h
@@ -2,15 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CONTENT_BROWSER_BLOB_STORAGE_BLOB_STORAGE_CONTEXT_WRAPPER_H_ -#define CONTENT_BROWSER_BLOB_STORAGE_BLOB_STORAGE_CONTEXT_WRAPPER_H_ +#ifndef CONTENT_BROWSER_CACHE_STORAGE_BLOB_STORAGE_CONTEXT_WRAPPER_H_ +#define CONTENT_BROWSER_CACHE_STORAGE_BLOB_STORAGE_CONTEXT_WRAPPER_H_ #include <string> #include "base/macros.h" #include "components/services/storage/public/mojom/blob_storage_context.mojom.h" #include "content/common/content_export.h" -#include "content/public/browser/browser_thread.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/remote.h" @@ -41,4 +40,4 @@ } // namespace content -#endif // CONTENT_BROWSER_BLOB_STORAGE_BLOB_STORAGE_CONTEXT_WRAPPER_H_ +#endif // CONTENT_BROWSER_CACHE_STORAGE_BLOB_STORAGE_CONTEXT_WRAPPER_H_
diff --git a/content/browser/cache_storage/cache_storage_cache_entry_handler.h b/content/browser/cache_storage/cache_storage_cache_entry_handler.h index 9a780db..b339787 100644 --- a/content/browser/cache_storage/cache_storage_cache_entry_handler.h +++ b/content/browser/cache_storage/cache_storage_cache_entry_handler.h
@@ -14,6 +14,7 @@ #include "base/memory/weak_ptr.h" #include "base/types/pass_key.h" #include "components/services/storage/public/mojom/cache_storage_control.mojom.h" +#include "content/browser/cache_storage/blob_storage_context_wrapper.h" #include "content/browser/cache_storage/cache_storage_cache.h" #include "content/browser/cache_storage/cache_storage_cache_handle.h" #include "content/browser/cache_storage/cache_storage_manager.h"
diff --git a/content/browser/cache_storage/cache_storage_context_impl.cc b/content/browser/cache_storage/cache_storage_context_impl.cc index 37ff15f..60e526d 100644 --- a/content/browser/cache_storage/cache_storage_context_impl.cc +++ b/content/browser/cache_storage/cache_storage_context_impl.cc
@@ -12,7 +12,7 @@ #include "build/build_config.h" #include "components/services/storage/public/mojom/quota_client.mojom.h" #include "components/services/storage/public/mojom/storage_usage_info.mojom.h" -#include "content/browser/blob_storage/chrome_blob_storage_context.h" +#include "content/browser/cache_storage/blob_storage_context_wrapper.h" #include "content/browser/cache_storage/cache_storage_dispatcher_host.h" #include "content/browser/cache_storage/cache_storage_quota_client.h" #include "content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h" @@ -20,7 +20,6 @@ #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" -#include "storage/browser/blob/blob_storage_context.h" #include "storage/browser/quota/quota_client_type.h" #include "storage/browser/quota/quota_manager_proxy.h" #include "storage/browser/quota/special_storage_policy.h" @@ -53,7 +52,9 @@ void CacheStorageContextImpl::Init( const base::FilePath& user_data_directory, scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, - scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy) { + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, + mojo::PendingRemote<storage::mojom::BlobStorageContext> + blob_storage_context) { DCHECK_CURRENTLY_ON(BrowserThread::UI); is_incognito_ = user_data_directory.empty(); @@ -69,18 +70,7 @@ base::BindOnce( &CacheStorageContextImpl::CreateCacheStorageManagerOnTaskRunner, this, user_data_directory, std::move(cache_task_runner), - quota_manager_proxy)); - - // If our target sequence is the IO thread, then the manager is guaranteed to - // be created before this task fires to create the quota clients. If we are - // running with a different target sequence then the quota client code will - // get a cross-sequence wrapper that is guaranteed to initialize its internal - // SequenceBound<> object after the real manager is created. - GetIOThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce(&CacheStorageContextImpl::CreateQuotaClientsOnIOThread, - base::WrapRefCounted(this), - std::move(quota_manager_proxy))); + quota_manager_proxy, std::move(blob_storage_context))); } void CacheStorageContextImpl::Shutdown() { @@ -151,32 +141,6 @@ this); } -void CacheStorageContextImpl::SetBlobParametersForCache( - ChromeBlobStorageContext* blob_storage_context) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - if (!blob_storage_context) - return; - - // TODO(enne): this remote will need to be sent to the storage service when - // cache storage is moved. - mojo::PendingRemote<storage::mojom::BlobStorageContext> remote; - auto receiver = remote.InitWithNewPipeAndPassReceiver(); - task_runner_->PostTask( - FROM_HERE, - base::BindOnce( - &CacheStorageContextImpl::SetBlobParametersForCacheOnTaskRunner, this, - std::move(remote))); - - // We can only bind a mojo interface for BlobStorageContext on the IO thread. - // TODO(enne): clean this up in the future to not require this bounce and - // to have this mojo context live on the cache storage sequence. - GetIOThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce( - &CacheStorageContextImpl::BindBlobStorageMojoContextOnIOThread, this, - base::RetainedRef(blob_storage_context), std::move(receiver))); -} - void CacheStorageContextImpl::GetAllOriginsInfo( storage::mojom::CacheStorageControl::GetAllOriginsInfoCallback callback) { // Can be called on any sequence. @@ -247,13 +211,37 @@ void CacheStorageContextImpl::CreateCacheStorageManagerOnTaskRunner( const base::FilePath& user_data_directory, scoped_refptr<base::SequencedTaskRunner> cache_task_runner, - scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy) { + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, + mojo::PendingRemote<storage::mojom::BlobStorageContext> + blob_storage_context) { DCHECK(task_runner_->RunsTasksInCurrentSequence()); DCHECK(!cache_manager_); cache_manager_ = LegacyCacheStorageManager::Create( user_data_directory, std::move(cache_task_runner), task_runner_, - quota_manager_proxy); + quota_manager_proxy, + base::MakeRefCounted<BlobStorageContextWrapper>( + std::move(blob_storage_context))); + + mojo::PendingRemote<storage::mojom::QuotaClient> cache_storage_client; + mojo::MakeSelfOwnedReceiver( + std::make_unique<CacheStorageQuotaClient>( + cache_manager_, storage::mojom::CacheStorageOwner::kCacheAPI), + cache_storage_client.InitWithNewPipeAndPassReceiver()); + quota_manager_proxy->RegisterClient( + std::move(cache_storage_client), + storage::QuotaClientType::kServiceWorkerCache, + {blink::mojom::StorageType::kTemporary}); + + mojo::PendingRemote<storage::mojom::QuotaClient> background_fetch_client; + mojo::MakeSelfOwnedReceiver( + std::make_unique<CacheStorageQuotaClient>( + cache_manager_, storage::mojom::CacheStorageOwner::kBackgroundFetch), + background_fetch_client.InitWithNewPipeAndPassReceiver()); + quota_manager_proxy->RegisterClient( + std::move(background_fetch_client), + storage::QuotaClientType::kBackgroundFetch, + {blink::mojom::StorageType::kTemporary}); } void CacheStorageContextImpl::ShutdownOnTaskRunner() { @@ -302,53 +290,4 @@ cache_manager_ = nullptr; } -void CacheStorageContextImpl::BindBlobStorageMojoContextOnIOThread( - ChromeBlobStorageContext* blob_storage_context, - mojo::PendingReceiver<storage::mojom::BlobStorageContext> receiver) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - DCHECK(blob_storage_context); - DCHECK(receiver.is_valid()); - - blob_storage_context->BindMojoContext(std::move(receiver)); -} - -void CacheStorageContextImpl::SetBlobParametersForCacheOnTaskRunner( - mojo::PendingRemote<storage::mojom::BlobStorageContext> remote) { - DCHECK(task_runner_->RunsTasksInCurrentSequence()); - if (!cache_manager_) - return; - cache_manager_->SetBlobParametersForCache( - base::MakeRefCounted<BlobStorageContextWrapper>(std::move(remote))); -} - -void CacheStorageContextImpl::CreateQuotaClientsOnIOThread( - scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - if (!quota_manager_proxy.get()) - return; - scoped_refptr<CacheStorageManager> manager = CacheManager(); - if (!manager) - return; - - mojo::PendingRemote<storage::mojom::QuotaClient> cache_storage_client; - mojo::MakeSelfOwnedReceiver( - std::make_unique<CacheStorageQuotaClient>( - manager, storage::mojom::CacheStorageOwner::kCacheAPI), - cache_storage_client.InitWithNewPipeAndPassReceiver()); - quota_manager_proxy->RegisterClient( - std::move(cache_storage_client), - storage::QuotaClientType::kServiceWorkerCache, - {blink::mojom::StorageType::kTemporary}); - - mojo::PendingRemote<storage::mojom::QuotaClient> background_fetch_client; - mojo::MakeSelfOwnedReceiver( - std::make_unique<CacheStorageQuotaClient>( - manager, storage::mojom::CacheStorageOwner::kBackgroundFetch), - background_fetch_client.InitWithNewPipeAndPassReceiver()); - quota_manager_proxy->RegisterClient( - std::move(background_fetch_client), - storage::QuotaClientType::kBackgroundFetch, - {blink::mojom::StorageType::kTemporary}); -} - } // namespace content
diff --git a/content/browser/cache_storage/cache_storage_context_impl.h b/content/browser/cache_storage/cache_storage_context_impl.h index 74872b7..2fceab1 100644 --- a/content/browser/cache_storage/cache_storage_context_impl.h +++ b/content/browser/cache_storage/cache_storage_context_impl.h
@@ -39,7 +39,6 @@ namespace content { -class ChromeBlobStorageContext; class CacheStorageDispatcherHost; class CacheStorageManager; @@ -72,7 +71,9 @@ // storagepartition is being setup and torn down. void Init(const base::FilePath& user_data_directory, scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, - scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy); + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, + mojo::PendingRemote<storage::mojom::BlobStorageContext> + blob_storage_context); void Shutdown(); void Bind(mojo::PendingReceiver<storage::mojom::CacheStorageControl> control); @@ -103,13 +104,6 @@ bool is_incognito() const { return is_incognito_; } - // This function must be called after this object is created but before any - // CacheStorageCache operations. It must be called on the UI thread. If - // |blob_storage_context| is NULL the function immediately returns without - // forwarding to the CacheStorageManager. - void SetBlobParametersForCache( - ChromeBlobStorageContext* blob_storage_context); - protected: ~CacheStorageContextImpl() override; @@ -117,20 +111,12 @@ void CreateCacheStorageManagerOnTaskRunner( const base::FilePath& user_data_directory, scoped_refptr<base::SequencedTaskRunner> cache_task_runner, - scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy); + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, + mojo::PendingRemote<storage::mojom::BlobStorageContext> + blob_storage_context); void ShutdownOnTaskRunner(); - void BindBlobStorageMojoContextOnIOThread( - ChromeBlobStorageContext* blob_storage_context, - mojo::PendingReceiver<storage::mojom::BlobStorageContext> receiver); - - void SetBlobParametersForCacheOnTaskRunner( - mojo::PendingRemote<storage::mojom::BlobStorageContext> remote); - - void CreateQuotaClientsOnIOThread( - scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy); - // Initialized at construction. const scoped_refptr<base::SequencedTaskRunner> task_runner_;
diff --git a/content/browser/cache_storage/cache_storage_manager.h b/content/browser/cache_storage/cache_storage_manager.h index 52d3f89d5..52d7589 100644 --- a/content/browser/cache_storage/cache_storage_manager.h +++ b/content/browser/cache_storage/cache_storage_manager.h
@@ -10,7 +10,6 @@ #include "base/macros.h" #include "components/services/storage/public/mojom/cache_storage_control.mojom.h" #include "components/services/storage/public/mojom/quota_client.mojom.h" -#include "content/browser/blob_storage/blob_storage_context_wrapper.h" #include "content/browser/cache_storage/cache_storage_handle.h" #include "content/common/content_export.h" #include "content/public/browser/browser_thread.h" @@ -60,10 +59,6 @@ virtual void AddObserver( mojo::PendingRemote<storage::mojom::CacheStorageObserver> observer) = 0; - // This must be called before any of the public Cache functions above. - virtual void SetBlobParametersForCache( - scoped_refptr<BlobStorageContextWrapper> blob_storage_context) = 0; - static bool IsValidQuotaOrigin(const url::Origin& origin); protected:
diff --git a/content/browser/cache_storage/cache_storage_manager_unittest.cc b/content/browser/cache_storage/cache_storage_manager_unittest.cc index 11d921e..15afd2b 100644 --- a/content/browser/cache_storage/cache_storage_manager_unittest.cc +++ b/content/browser/cache_storage/cache_storage_manager_unittest.cc
@@ -418,8 +418,8 @@ auto legacy_manager = LegacyCacheStorageManager::Create( temp_dir_path, base::ThreadTaskRunnerHandle::Get(), - base::ThreadTaskRunnerHandle::Get(), quota_manager_proxy_); - legacy_manager->SetBlobParametersForCache(blob_storage_context_); + base::ThreadTaskRunnerHandle::Get(), quota_manager_proxy_, + blob_storage_context_); switch (ManagerType()) { case TestManager::kLegacy:
diff --git a/content/browser/cache_storage/cache_storage_quota_client.cc b/content/browser/cache_storage/cache_storage_quota_client.cc index 0a9c34a..0e51d008 100644 --- a/content/browser/cache_storage/cache_storage_quota_client.cc +++ b/content/browser/cache_storage/cache_storage_quota_client.cc
@@ -5,7 +5,6 @@ #include "content/browser/cache_storage/cache_storage_quota_client.h" #include "content/browser/cache_storage/cache_storage_manager.h" -#include "content/public/browser/browser_thread.h" #include "storage/browser/quota/quota_client_type.h" #include "third_party/blink/public/mojom/quota/quota_types.mojom.h" #include "url/origin.h" @@ -15,14 +14,18 @@ CacheStorageQuotaClient::CacheStorageQuotaClient( scoped_refptr<CacheStorageManager> cache_manager, storage::mojom::CacheStorageOwner owner) - : cache_manager_(std::move(cache_manager)), owner_(owner) {} + : cache_manager_(std::move(cache_manager)), owner_(owner) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} -CacheStorageQuotaClient::~CacheStorageQuotaClient() = default; +CacheStorageQuotaClient::~CacheStorageQuotaClient() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} void CacheStorageQuotaClient::GetOriginUsage(const url::Origin& origin, blink::mojom::StorageType type, GetOriginUsageCallback callback) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_EQ(type, blink::mojom::StorageType::kTemporary); if (!CacheStorageManager::IsValidQuotaOrigin(origin)) { @@ -36,7 +39,7 @@ void CacheStorageQuotaClient::GetOriginsForType( blink::mojom::StorageType type, GetOriginsForTypeCallback callback) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_EQ(type, blink::mojom::StorageType::kTemporary); cache_manager_->GetOrigins(owner_, std::move(callback)); @@ -46,7 +49,7 @@ blink::mojom::StorageType type, const std::string& host, GetOriginsForHostCallback callback) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_EQ(type, blink::mojom::StorageType::kTemporary); cache_manager_->GetOriginsForHost(host, owner_, std::move(callback)); @@ -56,7 +59,7 @@ const url::Origin& origin, blink::mojom::StorageType type, DeleteOriginDataCallback callback) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_EQ(type, blink::mojom::StorageType::kTemporary); if (!CacheStorageManager::IsValidQuotaOrigin(origin)) { @@ -70,6 +73,7 @@ void CacheStorageQuotaClient::PerformStorageCleanup( blink::mojom::StorageType type, PerformStorageCleanupCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); std::move(callback).Run(); }
diff --git a/content/browser/cache_storage/cache_storage_quota_client.h b/content/browser/cache_storage/cache_storage_quota_client.h index c7ade58..223f1c7 100644 --- a/content/browser/cache_storage/cache_storage_quota_client.h +++ b/content/browser/cache_storage/cache_storage_quota_client.h
@@ -7,6 +7,7 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "base/sequence_checker.h" #include "components/services/storage/public/mojom/cache_storage_control.mojom.h" #include "components/services/storage/public/mojom/quota_client.mojom.h" #include "content/common/content_export.h" @@ -18,9 +19,11 @@ class CacheStorageManager; -// CacheStorageQuotaClient is owned by the QuotaManager. There is one per -// CacheStorageManager/CacheStorageOwner tuple. Created and accessed on -// the IO thread. +// CacheStorageQuotaClient is a self-owned receiver created by +// CacheStorageContextImpl. The remote end is owned by QuotaManagerProxy. +// There is one CacheStorageQuotaClient per CacheStorageManager / +// CacheStorageOwner tuple. Created and accessed on the cache storage task +// runner. class CONTENT_EXPORT CacheStorageQuotaClient : public storage::mojom::QuotaClient { public: @@ -50,6 +53,8 @@ const scoped_refptr<CacheStorageManager> cache_manager_; const storage::mojom::CacheStorageOwner owner_; + SEQUENCE_CHECKER(sequence_checker_); + DISALLOW_COPY_AND_ASSIGN(CacheStorageQuotaClient); };
diff --git a/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.cc b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.cc index dea31758..8c4ddb7 100644 --- a/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.cc +++ b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.cc
@@ -150,14 +150,6 @@ inner_.Post(FROM_HERE, &Inner::AddObserver, std::move(observer)); } -void CrossSequenceCacheStorageManager::SetBlobParametersForCache( - scoped_refptr<BlobStorageContextWrapper> blob_storage_context) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // This method is used for initialization of a real manager and should not - // be invoked for the cross-sequence wrapper. - NOTREACHED(); -} - CrossSequenceCacheStorageManager::~CrossSequenceCacheStorageManager() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
diff --git a/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h index 9ec3431..c69aaa9 100644 --- a/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h +++ b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h
@@ -55,8 +55,6 @@ storage::mojom::CacheStorageOwner owner) override; void AddObserver(mojo::PendingRemote<storage::mojom::CacheStorageObserver> observer) override; - void SetBlobParametersForCache( - scoped_refptr<BlobStorageContextWrapper> blob_storage_context) override; private: ~CrossSequenceCacheStorageManager() override;
diff --git a/content/browser/cache_storage/legacy/legacy_cache_storage.h b/content/browser/cache_storage/legacy/legacy_cache_storage.h index 516ca13..a309fc1 100644 --- a/content/browser/cache_storage/legacy/legacy_cache_storage.h +++ b/content/browser/cache_storage/legacy/legacy_cache_storage.h
@@ -18,6 +18,7 @@ #include "build/build_config.h" #include "components/services/storage/public/mojom/blob_storage_context.mojom.h" #include "components/services/storage/public/mojom/cache_storage_control.mojom.h" +#include "content/browser/cache_storage/blob_storage_context_wrapper.h" #include "content/browser/cache_storage/cache_storage.h" #include "content/browser/cache_storage/cache_storage_cache_observer.h" #include "content/browser/cache_storage/cache_storage_manager.h"
diff --git a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc index c83afd4..e4b630a 100644 --- a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc +++ b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc
@@ -51,7 +51,6 @@ #include "net/http/http_response_headers.h" #include "net/http/http_status_code.h" #include "services/network/public/mojom/fetch_api.mojom.h" -#include "storage/browser/blob/blob_storage_context.h" #include "storage/browser/quota/padding_key.h" #include "storage/browser/quota/quota_manager_proxy.h" #include "third_party/blink/public/common/cache_storage/cache_storage_utils.h"
diff --git a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.h b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.h index a6e7a2b..b6d7e13 100644 --- a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.h +++ b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.h
@@ -18,6 +18,7 @@ #include "base/memory/weak_ptr.h" #include "base/optional.h" #include "components/services/storage/public/mojom/cache_storage_control.mojom.h" +#include "content/browser/cache_storage/blob_storage_context_wrapper.h" #include "content/browser/cache_storage/cache_storage_cache.h" #include "content/browser/cache_storage/cache_storage_handle.h" #include "content/browser/cache_storage/cache_storage_manager.h"
diff --git a/content/browser/cache_storage/legacy/legacy_cache_storage_manager.cc b/content/browser/cache_storage/legacy/legacy_cache_storage_manager.cc index bc889a8b..f2e6dc8 100644 --- a/content/browser/cache_storage/legacy/legacy_cache_storage_manager.cc +++ b/content/browser/cache_storage/legacy/legacy_cache_storage_manager.cc
@@ -244,7 +244,8 @@ const base::FilePath& path, scoped_refptr<base::SequencedTaskRunner> cache_task_runner, scoped_refptr<base::SequencedTaskRunner> scheduler_task_runner, - scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy) { + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, + scoped_refptr<BlobStorageContextWrapper> blob_storage_context) { base::FilePath root_path = path; if (!path.empty()) { root_path = path.Append(storage::kServiceWorkerDirectory) @@ -253,7 +254,7 @@ return base::WrapRefCounted(new LegacyCacheStorageManager( root_path, std::move(cache_task_runner), std::move(scheduler_task_runner), - std::move(quota_manager_proxy))); + std::move(quota_manager_proxy), std::move(blob_storage_context))); } // static @@ -264,8 +265,8 @@ new LegacyCacheStorageManager(old_manager->root_path(), old_manager->cache_task_runner(), old_manager->scheduler_task_runner(), - old_manager->quota_manager_proxy_.get())); - manager->SetBlobParametersForCache(old_manager->blob_storage_context_); + old_manager->quota_manager_proxy_, + old_manager->blob_storage_context_)); return manager; } @@ -300,15 +301,6 @@ return it->second.get()->CreateHandle(); } -void LegacyCacheStorageManager::SetBlobParametersForCache( - scoped_refptr<BlobStorageContextWrapper> blob_storage_context) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(cache_storage_map_.empty()); - DCHECK(!blob_storage_context_ || - blob_storage_context_ == blob_storage_context); - blob_storage_context_ = std::move(blob_storage_context); -} - void LegacyCacheStorageManager::NotifyCacheListChanged( const url::Origin& origin) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -530,11 +522,13 @@ const base::FilePath& path, scoped_refptr<base::SequencedTaskRunner> cache_task_runner, scoped_refptr<base::SequencedTaskRunner> scheduler_task_runner, - scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy) + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, + scoped_refptr<BlobStorageContextWrapper> blob_storage_context) : root_path_(path), cache_task_runner_(std::move(cache_task_runner)), scheduler_task_runner_(std::move(scheduler_task_runner)), - quota_manager_proxy_(std::move(quota_manager_proxy)) {} + quota_manager_proxy_(std::move(quota_manager_proxy)), + blob_storage_context_(std::move(blob_storage_context)) {} // static base::FilePath LegacyCacheStorageManager::ConstructOriginPath(
diff --git a/content/browser/cache_storage/legacy/legacy_cache_storage_manager.h b/content/browser/cache_storage/legacy/legacy_cache_storage_manager.h index 6b7127ad..396323ea 100644 --- a/content/browser/cache_storage/legacy/legacy_cache_storage_manager.h +++ b/content/browser/cache_storage/legacy/legacy_cache_storage_manager.h
@@ -18,6 +18,7 @@ #include "components/services/storage/public/mojom/cache_storage_control.mojom.h" #include "components/services/storage/public/mojom/quota_client.mojom.h" #include "components/services/storage/public/mojom/storage_usage_info.mojom.h" +#include "content/browser/cache_storage/blob_storage_context_wrapper.h" #include "content/browser/cache_storage/cache_storage_context_impl.h" #include "content/browser/cache_storage/cache_storage_manager.h" #include "content/browser/cache_storage/legacy/legacy_cache_storage.h" @@ -46,7 +47,8 @@ const base::FilePath& path, scoped_refptr<base::SequencedTaskRunner> cache_task_runner, scoped_refptr<base::SequencedTaskRunner> scheduler_task_runner, - scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy); + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, + scoped_refptr<BlobStorageContextWrapper> blob_storage_context); // Create a new manager using the underlying configuration of the given // manager, but with its own list of storage objects. This is only used @@ -90,9 +92,6 @@ void AddObserver(mojo::PendingRemote<storage::mojom::CacheStorageObserver> observer) override; - void SetBlobParametersForCache( - scoped_refptr<BlobStorageContextWrapper> blob_storage_context) override; - void NotifyCacheListChanged(const url::Origin& origin); void NotifyCacheContentChanged(const url::Origin& origin, const std::string& name); @@ -117,7 +116,8 @@ const base::FilePath& path, scoped_refptr<base::SequencedTaskRunner> cache_task_runner, scoped_refptr<base::SequencedTaskRunner> scheduler_task_runner, - scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy); + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, + scoped_refptr<BlobStorageContextWrapper> blob_storage_context); ~LegacyCacheStorageManager() override;
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc index 6be7f55..5588e67 100644 --- a/content/browser/child_process_security_policy_impl.cc +++ b/content/browser/child_process_security_policy_impl.cc
@@ -1615,7 +1615,14 @@ // BrowsingInstances are registered in the process. Allow this for now, // to maintain legacy behavior, until we rule out all the ways it can // happen. - return true; + RenderProcessHostImpl* child_host = static_cast<RenderProcessHostImpl*>( + RenderProcessHost::FromID(child_id)); + DCHECK(child_host); + size_t keep_alive_count = child_host->keep_alive_ref_count(); + failure_reason = base::StringPrintf("No BIids, keep_alive_count = %zu", + keep_alive_count); + // This will fall through to the call to the call to + // LogCanAccessDataForOriginCrashKeys and log the failure reason. } for (auto browsing_instance_id : security_state->browsing_instance_ids()) {
diff --git a/content/browser/devtools/protocol/browser_handler.cc b/content/browser/devtools/protocol/browser_handler.cc index db7ee33..c42c3d9 100644 --- a/content/browser/devtools/protocol/browser_handler.cc +++ b/content/browser/devtools/protocol/browser_handler.cc
@@ -511,7 +511,7 @@ if (command_line->HasSwitch(switches::kEnableAutomation)) { for (const auto& arg : command_line->argv()) { #if defined(OS_WIN) - (*arguments)->emplace_back(base::UTF16ToUTF8(arg)); + (*arguments)->emplace_back(base::WideToUTF8(arg)); #else (*arguments)->emplace_back(arg); #endif
diff --git a/content/browser/download/drag_download_util.cc b/content/browser/download/drag_download_util.cc index bc40cd3..92f4328 100644 --- a/content/browser/download/drag_download_util.cc +++ b/content/browser/download/drag_download_util.cc
@@ -46,11 +46,7 @@ if (file_name) { base::string16 file_name_str = metadata.substr( mime_type_end_pos + 1, file_name_end_pos - mime_type_end_pos - 1); -#if defined(OS_WIN) - *file_name = base::FilePath(file_name_str); -#else - *file_name = base::FilePath(base::UTF16ToUTF8(file_name_str)); -#endif + *file_name = base::FilePath::FromUTF16Unsafe(file_name_str); } if (url) *url = parsed_url; @@ -68,10 +64,9 @@ new_file_path = *file_path; } else { #if defined(OS_WIN) - base::string16 suffix = - base::ASCIIToUTF16("-") + base::NumberToString16(seq); + std::wstring suffix = L"-" + base::NumberToWString(seq); #else - std::string suffix = std::string("-") + base::NumberToString(seq); + std::string suffix = "-" + base::NumberToString(seq); #endif new_file_path = file_path->InsertBeforeExtension(suffix); }
diff --git a/content/browser/file_system/file_system_manager_impl.cc b/content/browser/file_system/file_system_manager_impl.cc index 1e4c18e..92b55f10 100644 --- a/content/browser/file_system/file_system_manager_impl.cc +++ b/content/browser/file_system/file_system_manager_impl.cc
@@ -86,14 +86,14 @@ case storage::FileSystemType::kFileSystemTypeUnknown: case storage::FileSystemType::kFileSystemInternalTypeEnumStart: case storage::FileSystemType::kFileSystemTypeTest: - case storage::FileSystemType::kFileSystemTypeNativeLocal: - case storage::FileSystemType::kFileSystemTypeRestrictedNativeLocal: + case storage::FileSystemType::kFileSystemTypeLocal: + case storage::FileSystemType::kFileSystemTypeRestrictedLocal: case storage::FileSystemType::kFileSystemTypeDragged: - case storage::FileSystemType::kFileSystemTypeNativeMedia: + case storage::FileSystemType::kFileSystemTypeLocalMedia: case storage::FileSystemType::kFileSystemTypeDeviceMedia: case storage::FileSystemType::kFileSystemTypeSyncable: case storage::FileSystemType::kFileSystemTypeSyncableForInternalSync: - case storage::FileSystemType::kFileSystemTypeNativeForPlatformApp: + case storage::FileSystemType::kFileSystemTypeLocalForPlatformApp: case storage::FileSystemType::kFileSystemTypeForTransientFile: case storage::FileSystemType::kFileSystemTypePluginPrivate: case storage::FileSystemType::kFileSystemTypeCloudDevice:
diff --git a/content/browser/file_system_access/file_system_access_directory_handle_impl.cc b/content/browser/file_system_access/file_system_access_directory_handle_impl.cc index d7d4dccd..dedf355 100644 --- a/content/browser/file_system_access/file_system_access_directory_handle_impl.cc +++ b/content/browser/file_system_access/file_system_access_directory_handle_impl.cc
@@ -260,7 +260,7 @@ std::vector<std::string> result; result.reserve(components.size()); for (const auto& component : components) { - result.push_back(base::UTF16ToUTF8(component)); + result.push_back(base::WideToUTF8(component)); } std::move(callback).Run(file_system_access_error::Ok(), std::move(result)); #else
diff --git a/content/browser/file_system_access/file_system_access_file_writer_impl.cc b/content/browser/file_system_access/file_system_access_file_writer_impl.cc index 300c7748..714d58fe 100644 --- a/content/browser/file_system_access/file_system_access_file_writer_impl.cc +++ b/content/browser/file_system_access/file_system_access_file_writer_impl.cc
@@ -654,7 +654,7 @@ target_url.type() != storage::kFileSystemTypePersistent) << target_url.type(); #else - DCHECK(target_url.type() == storage::kFileSystemTypeNativeLocal || + DCHECK(target_url.type() == storage::kFileSystemTypeLocal || target_url.type() == storage::kFileSystemTypeTest) << target_url.type(); #endif
diff --git a/content/browser/file_system_access/file_system_access_file_writer_impl_unittest.cc b/content/browser/file_system_access/file_system_access_file_writer_impl_unittest.cc index f7bbc798..6821b9e 100644 --- a/content/browser/file_system_access/file_system_access_file_writer_impl_unittest.cc +++ b/content/browser/file_system_access/file_system_access_file_writer_impl_unittest.cc
@@ -127,7 +127,7 @@ std::string base_name; IsolatedContext::ScopedFSHandle fs = isolated_context->RegisterFileSystemForPath( - storage::kFileSystemTypeNativeLocal, std::string(), dir_.GetPath(), + storage::kFileSystemTypeLocal, std::string(), dir_.GetPath(), &base_name); base::FilePath root_path = isolated_context->CreateVirtualRootPath(fs.id()).AppendASCII(base_name);
diff --git a/content/browser/file_system_access/file_system_access_handle_base.cc b/content/browser/file_system_access/file_system_access_handle_base.cc index 16c82f47..438d629 100644 --- a/content/browser/file_system_access/file_system_access_handle_base.cc +++ b/content/browser/file_system_access/file_system_access_handle_base.cc
@@ -28,7 +28,7 @@ << url_.mount_type(); // We support sandboxed file system and local file systems on all platforms. - DCHECK(url_.type() == storage::kFileSystemTypeNativeLocal || + DCHECK(url_.type() == storage::kFileSystemTypeLocal || url_.type() == storage::kFileSystemTypeTemporary || url_.mount_type() == storage::kFileSystemTypeExternal || url_.type() == storage::kFileSystemTypeTest) @@ -39,7 +39,7 @@ url_.mount_type() == storage::kFileSystemTypeExternal) << url_.mount_type(); if (url_.mount_type() == storage::kFileSystemTypeIsolated) - DCHECK_EQ(url_.type(), storage::kFileSystemTypeNativeLocal); + DCHECK_EQ(url_.type(), storage::kFileSystemTypeLocal); Observe(WebContentsImpl::FromRenderFrameHostID(context_.frame_id));
diff --git a/content/browser/file_system_access/file_system_access_manager_impl.cc b/content/browser/file_system_access/file_system_access_manager_impl.cc index ddffad28..58efc47 100644 --- a/content/browser/file_system_access/file_system_access_manager_impl.cc +++ b/content/browser/file_system_access/file_system_access_manager_impl.cc
@@ -569,7 +569,7 @@ ? FileSystemAccessHandleData::kFile : FileSystemAccessHandleData::kDirectory); - if (url.type() == storage::kFileSystemTypeNativeLocal || + if (url.type() == storage::kFileSystemTypeLocal || url.mount_type() == storage::kFileSystemTypeExternal) { // A url can have mount_type = external and type = native local at the same // time. In that case we want to still treat it as an external path. @@ -1179,7 +1179,7 @@ FileSystemURLAndFSHandle result; result.file_system = isolated_context->RegisterFileSystemForPath( - storage::kFileSystemTypeNativeLocal, std::string(), path, + storage::kFileSystemTypeLocal, std::string(), path, &result.base_name); base::FilePath root_path =
diff --git a/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc b/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc index c7d944e..41cc077c 100644 --- a/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc +++ b/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc
@@ -134,7 +134,7 @@ // path round trips as an external path, even if the path resolves to a // local path. storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - kTestMountPoint, storage::kFileSystemTypeNativeLocal, + kTestMountPoint, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), dir_.GetPath()); chrome_blob_context_ = base::MakeRefCounted<ChromeBlobStorageContext>(); @@ -737,7 +737,7 @@ const storage::FileSystemURL& url = token->url(); EXPECT_EQ(kTestOrigin, url.origin()); EXPECT_EQ(kTestPath, url.path()); - EXPECT_EQ(storage::kFileSystemTypeNativeLocal, url.type()); + EXPECT_EQ(storage::kFileSystemTypeLocal, url.type()); EXPECT_EQ(storage::kFileSystemTypeIsolated, url.mount_type()); EXPECT_EQ(HandleType::kFile, token->type()); EXPECT_EQ(ask_grant_, token->GetReadGrant()); @@ -773,7 +773,7 @@ const storage::FileSystemURL& url = token->url(); EXPECT_EQ(kTestOrigin, url.origin()); EXPECT_EQ(kTestPath, url.path()); - EXPECT_EQ(storage::kFileSystemTypeNativeLocal, url.type()); + EXPECT_EQ(storage::kFileSystemTypeLocal, url.type()); EXPECT_EQ(storage::kFileSystemTypeIsolated, url.mount_type()); EXPECT_EQ(HandleType::kDirectory, token->type()); EXPECT_EQ(ask_grant_, token->GetReadGrant()); @@ -829,7 +829,7 @@ EXPECT_EQ(kTestOrigin, url.origin()); EXPECT_EQ(kDirectoryPath.Append(base::FilePath::FromUTF8Unsafe(kTestName)), url.path()); - EXPECT_EQ(storage::kFileSystemTypeNativeLocal, url.type()); + EXPECT_EQ(storage::kFileSystemTypeLocal, url.type()); EXPECT_EQ(storage::kFileSystemTypeIsolated, url.mount_type()); EXPECT_EQ(HandleType::kFile, token->type()); EXPECT_EQ(ask_grant_, token->GetReadGrant()); @@ -884,7 +884,7 @@ const storage::FileSystemURL& url = token->url(); EXPECT_EQ(kTestOrigin, url.origin()); EXPECT_EQ(kDirectoryPath.AppendASCII(kTestName), url.path()); - EXPECT_EQ(storage::kFileSystemTypeNativeLocal, url.type()); + EXPECT_EQ(storage::kFileSystemTypeLocal, url.type()); EXPECT_EQ(storage::kFileSystemTypeIsolated, url.mount_type()); EXPECT_EQ(HandleType::kDirectory, token->type()); EXPECT_EQ(ask_grant_, token->GetReadGrant());
diff --git a/content/browser/file_system_access/file_system_chooser_browsertest.cc b/content/browser/file_system_access/file_system_chooser_browsertest.cc index 566d9f99..816270d1 100644 --- a/content/browser/file_system_access/file_system_chooser_browsertest.cc +++ b/content/browser/file_system_access/file_system_chooser_browsertest.cc
@@ -55,7 +55,7 @@ // on all platforms. We're not testing more complicated ChromeOS specific // file system backends here. storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - kTestMountPoint, storage::kFileSystemTypeNativeLocal, + kTestMountPoint, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), temp_dir_.GetPath()); ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/content/browser/gpu/gpu_internals_ui.cc b/content/browser/gpu/gpu_internals_ui.cc index 7d9f2da..9b39b8a 100644 --- a/content/browser/gpu/gpu_internals_ui.cc +++ b/content/browser/gpu/gpu_internals_ui.cc
@@ -21,6 +21,7 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/stringize_macros.h" #include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" #include "base/system/sys_info.h" #include "base/values.h" #include "build/build_config.h" @@ -827,8 +828,13 @@ auto dict = std::make_unique<base::DictionaryValue>(); dict->SetString("version", GetContentClient()->browser()->GetProduct()); - dict->SetString("command_line", - base::CommandLine::ForCurrentProcess()->GetCommandLineString()); + base::CommandLine::StringType command_line = + base::CommandLine::ForCurrentProcess()->GetCommandLineString(); +#if defined(OS_WIN) + dict->SetString("command_line", base::WideToUTF8(command_line)); +#else + dict->SetString("command_line", command_line); +#endif dict->SetString("operating_system", base::SysInfo::OperatingSystemName() + " " + base::SysInfo::OperatingSystemVersion());
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc index d5d68aa5..a15a6e1 100644 --- a/content/browser/gpu/gpu_process_host.cc +++ b/content/browser/gpu/gpu_process_host.cc
@@ -434,7 +434,7 @@ policy->AddDllToUnload(L"cmsetac.dll"); if (cmd_line_.HasSwitch(switches::kEnableLogging)) { - base::string16 log_file_path = logging::GetLogFileFullPath(); + std::wstring log_file_path = logging::GetLogFileFullPath(); if (!log_file_path.empty()) { sandbox::ResultCode result = policy->AddRule( sandbox::TargetPolicy::SUBSYS_FILES, @@ -481,7 +481,7 @@ if (UseOpenGLRenderer()) return true; - return base::win::IsRunningUnderDesktopName(STRING16_LITERAL("winlogon")); + return base::win::IsRunningUnderDesktopName(L"winlogon"); } bool enable_appcontainer_; @@ -779,6 +779,7 @@ message = "The info collection GPU process "; } + bool unexpected_exit = false; switch (info.status) { case base::TERMINATION_STATUS_NORMAL_TERMINATION: // Don't block offscreen contexts (and force page reload for webgl) @@ -789,6 +790,7 @@ break; case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: message += base::StringPrintf("exited with code %d.", info.exit_code); + unexpected_exit = true; break; case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessTerminationOrigin", @@ -798,6 +800,7 @@ break; case base::TERMINATION_STATUS_PROCESS_CRASHED: message += "crashed!"; + unexpected_exit = true; break; case base::TERMINATION_STATUS_STILL_RUNNING: message += "hasn't exited yet."; @@ -805,28 +808,38 @@ #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM: message += "was killed due to out of memory."; + unexpected_exit = true; break; #endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) #if defined(OS_ANDROID) case base::TERMINATION_STATUS_OOM_PROTECTED: message += "was protected from out of memory kill."; + unexpected_exit = true; break; #endif // OS_ANDROID case base::TERMINATION_STATUS_LAUNCH_FAILED: message += "failed to start!"; + unexpected_exit = true; break; case base::TERMINATION_STATUS_OOM: message += "died due to out of memory."; + unexpected_exit = true; break; #if defined(OS_WIN) case base::TERMINATION_STATUS_INTEGRITY_FAILURE: message += "failed integrity checks."; + unexpected_exit = true; break; #endif case base::TERMINATION_STATUS_MAX_ENUM: NOTREACHED(); break; } + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kForceBrowserCrashOnGpuCrash)) { + CHECK(!unexpected_exit) + << "Force Chrome to crash due to unexpected GPU process crash"; + } GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&OnGpuProcessHostDestroyedOnUI, host_id_, message));
diff --git a/content/browser/indexed_db/indexed_db_context_impl.cc b/content/browser/indexed_db/indexed_db_context_impl.cc index 542de51..fd11db5 100644 --- a/content/browser/indexed_db/indexed_db_context_impl.cc +++ b/content/browser/indexed_db/indexed_db_context_impl.cc
@@ -328,7 +328,7 @@ auto paths = std::make_unique<base::ListValue>(); if (!is_incognito()) { for (const base::FilePath& path : GetStoragePaths(origin)) - paths->AppendString(path.value()); + paths->AppendString(path.AsUTF8Unsafe()); } else { paths->AppendString("N/A"); }
diff --git a/content/browser/indexed_db/indexed_db_internals_ui.cc b/content/browser/indexed_db/indexed_db_internals_ui.cc index 8ba85ad..b42b2774 100644 --- a/content/browser/indexed_db/indexed_db_internals_ui.cc +++ b/content/browser/indexed_db/indexed_db_internals_ui.cc
@@ -104,7 +104,7 @@ const base::FilePath& path) { DCHECK_CURRENTLY_ON(BrowserThread::UI); web_ui()->CallJavascriptFunctionUnsafe("indexeddb.onOriginsReady", origins, - base::Value(path.value())); + base::Value(path.AsUTF8Unsafe())); } static void FindControl(const base::FilePath& partition_path, @@ -122,10 +122,10 @@ base::FilePath* partition_path, Origin* origin, storage::mojom::IndexedDBControl** control) { - base::FilePath::StringType path_string; + std::string path_string; if (!args->GetString(0, &path_string)) return false; - *partition_path = base::FilePath(path_string); + *partition_path = base::FilePath::FromUTF8Unsafe(path_string); std::string url_string; if (!args->GetString(1, &url_string)) @@ -239,7 +239,7 @@ if (!success) return; - const GURL url = GURL(FILE_PATH_LITERAL("file://") + zip_path.value()); + const GURL url = GURL("file://" + zip_path.AsUTF8Unsafe()); WebContents* web_contents = web_ui()->GetWebContents(); net::NetworkTrafficAnnotationTag traffic_annotation = net::DefineNetworkTrafficAnnotation("indexed_db_internals_handler", R"( @@ -341,7 +341,8 @@ item->AddObserver(new FileDeleter(temp_path)); web_ui()->CallJavascriptFunctionUnsafe( - "indexeddb.onOriginDownloadReady", base::Value(partition_path.value()), + "indexeddb.onOriginDownloadReady", + base::Value(partition_path.AsUTF8Unsafe()), base::Value(origin.Serialize()), base::Value(static_cast<double>(connection_count))); }
diff --git a/content/browser/installedapp/installed_app_provider_impl_win.cc b/content/browser/installedapp/installed_app_provider_impl_win.cc index 7cf00a4..0518912 100644 --- a/content/browser/installedapp/installed_app_provider_impl_win.cc +++ b/content/browser/installedapp/installed_app_provider_impl_win.cc
@@ -62,7 +62,7 @@ if (FAILED(hr)) continue; - base::string16 app_user_model_id( + std::wstring app_user_model_id( base::win::ScopedHString(app_user_model_id_native).Get()); size_t windows_app_count = 0; @@ -84,7 +84,7 @@ // https://docs.microsoft.com/en-us/uwp/schemas/ // appinstallerschema/element-package if (base::CompareCaseInsensitiveASCII( - related_app->id.value(), base::UTF16ToASCII(app_user_model_id)) == + related_app->id.value(), base::WideToASCII(app_user_model_id)) == 0) { auto application = blink::mojom::RelatedApplication::New(); application->platform = related_app->platform;
diff --git a/content/browser/loader/file_url_loader_factory.cc b/content/browser/loader/file_url_loader_factory.cc index beec643..f6a3e6f 100644 --- a/content/browser/loader/file_url_loader_factory.cc +++ b/content/browser/loader/file_url_loader_factory.cc
@@ -258,12 +258,7 @@ if (!wrote_header_) { wrote_header_ = true; -#if defined(OS_WIN) - const base::string16& title = path_.value(); -#elif defined(OS_POSIX) || defined(OS_FUCHSIA) - const base::string16& title = - base::WideToUTF16(base::SysNativeMBToWide(path_.value())); -#endif + const base::string16& title = path_.AsUTF16Unsafe(); pending_data_.append(net::GetDirectoryListingHeader(title)); // If not a top-level directory, add a link to the parent directory. To
diff --git a/content/browser/media/capture/desktop_capture_device_mac.cc b/content/browser/media/capture/desktop_capture_device_mac.cc index af24f75..29711048 100644 --- a/content/browser/media/capture/desktop_capture_device_mac.cc +++ b/content/browser/media/capture/desktop_capture_device_mac.cc
@@ -6,7 +6,7 @@ #include <CoreGraphics/CoreGraphics.h> -#include "base/threading/thread.h" +#include "base/threading/thread_checker.h" #include "media/capture/video/video_capture_device.h" #include "ui/gfx/native_widget_types.h" @@ -21,9 +21,23 @@ ~DesktopCaptureDeviceMac() override = default; + static float ComputeMinFrameRate(float requested_frame_rate) { + // Set a minimum frame rate of 5 fps, unless the requested frame rate is + // even lower. + constexpr float kMinFrameRate = 5.f; + + // Don't send frames at more than 80% the requested rate, because doing so + // can stochastically toggle between repeated and new frames. + constexpr float kRequestedFrameRateFactor = 0.8f; + + return std::min(requested_frame_rate * kRequestedFrameRateFactor, + kMinFrameRate); + } + // media::VideoCaptureDevice: void AllocateAndStart(const media::VideoCaptureParams& params, std::unique_ptr<Client> client) override { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(client && !client_); client_ = std::move(client); @@ -31,20 +45,14 @@ requested_format_.pixel_format = media::PIXEL_FORMAT_NV12; DCHECK_GT(requested_format_.frame_size.GetArea(), 0); DCHECK_GT(requested_format_.frame_rate, 0); + min_frame_rate_ = ComputeMinFrameRate(requested_format_.frame_rate); - auto task_runner = base::SequencedTaskRunnerHandle::Get(); - CGDisplayStreamFrameAvailableHandler handler = ^( - CGDisplayStreamFrameStatus status, uint64_t display_time, - IOSurfaceRef frame_surface, CGDisplayStreamUpdateRef update_ref) { - if (status == kCGDisplayStreamFrameStatusFrameComplete) { - task_runner->PostTask( - FROM_HERE, - base::BindOnce(&DesktopCaptureDeviceMac::OnReceivedIOSurface, - weak_factory_.GetWeakPtr(), - gfx::ScopedInUseIOSurface( - frame_surface, base::scoped_policy::RETAIN))); - } - }; + CGDisplayStreamFrameAvailableHandler handler = + ^(CGDisplayStreamFrameStatus status, uint64_t display_time, + IOSurfaceRef frame_surface, CGDisplayStreamUpdateRef update_ref) { + if (status == kCGDisplayStreamFrameStatusFrameComplete) + OnReceivedIOSurfaceFromStream(frame_surface); + }; base::ScopedCFTypeRef<CFDictionaryRef> properties; { @@ -93,6 +101,9 @@ client_->OnStarted(); } void StopAndDeAllocate() override { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + min_frame_rate_enforcement_timer_.reset(); weak_factory_.InvalidateWeakPtrs(); if (display_stream_) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), @@ -104,12 +115,22 @@ } private: - void OnReceivedIOSurface(gfx::ScopedInUseIOSurface io_surface) { - // Package |io_surface| as a GpuMemoryBuffer. + void OnReceivedIOSurfaceFromStream(IOSurfaceRef io_surface) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + last_received_io_surface_.reset(io_surface, base::scoped_policy::RETAIN); + + // Immediately send the new frame to the client. + SendLastReceivedIOSurfaceToClient(); + } + void SendLastReceivedIOSurfaceToClient() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + // Package |last_received_io_surface_| as a GpuMemoryBuffer. gfx::GpuMemoryBufferHandle handle; handle.id.id = -1; handle.type = gfx::GpuMemoryBufferType::IO_SURFACE_BUFFER; - handle.io_surface.reset(io_surface, base::scoped_policy::RETAIN); + handle.io_surface.reset(last_received_io_surface_, + base::scoped_policy::RETAIN); const auto now = base::TimeTicks::Now(); if (first_frame_time_.is_null()) @@ -118,18 +139,40 @@ client_->OnIncomingCapturedExternalBuffer( std::move(handle), requested_format_, gfx::ColorSpace::CreateSRGB(), now, now - first_frame_time_); + + // Reset |min_frame_rate_enforcement_timer_|. + if (!min_frame_rate_enforcement_timer_) { + min_frame_rate_enforcement_timer_ = + std::make_unique<base::RepeatingTimer>( + FROM_HERE, base::TimeDelta::FromSecondsD(1 / min_frame_rate_), + base::BindRepeating( + &DesktopCaptureDeviceMac::SendLastReceivedIOSurfaceToClient, + weak_factory_.GetWeakPtr())); + } + min_frame_rate_enforcement_timer_->Reset(); } + // This class assumes single threaded access. + THREAD_CHECKER(thread_checker_); + const CGDirectDisplayID display_id_; std::unique_ptr<Client> client_; base::ScopedCFTypeRef<CGDisplayStreamRef> display_stream_; media::VideoCaptureFormat requested_format_; + float min_frame_rate_ = 1.f; + gfx::ScopedInUseIOSurface last_received_io_surface_; - // The time of the first call to OnReceivedIOSurface. Used to compute the - // timestamp of subsequent frames. + // The time of the first call to SendLastReceivedIOSurfaceToClient. Used to + // compute the timestamp of subsequent frames. base::TimeTicks first_frame_time_; + // Timer to enforce |min_frame_rate_| by repeatedly calling + // SendLastReceivedIOSurfaceToClient. + // TODO(https://crbug.com/1171127): Remove the need for the capture device + // to re-submit static content. + std::unique_ptr<base::RepeatingTimer> min_frame_rate_enforcement_timer_; + base::WeakPtrFactory<DesktopCaptureDeviceMac> weak_factory_; DISALLOW_COPY_AND_ASSIGN(DesktopCaptureDeviceMac); };
diff --git a/content/browser/media/frameless_media_interface_proxy.cc b/content/browser/media/frameless_media_interface_proxy.cc index 161e2437..2582c4af 100644 --- a/content/browser/media/frameless_media_interface_proxy.cc +++ b/content/browser/media/frameless_media_interface_proxy.cc
@@ -73,6 +73,15 @@ renderer_extension_receiver) {} #endif // defined(OS_ANDROID) +#if defined(OS_WIN) +// Unimplemented method as this requires CDM and media::Renderer services with +// frame context. +void FramelessMediaInterfaceProxy::CreateMediaFoundationRenderer( + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) {} +#endif // defined(OS_WIN) + void FramelessMediaInterfaceProxy::CreateCdm(const std::string& key_system, const media::CdmConfig& cdm_config, CreateCdmCallback callback) {
diff --git a/content/browser/media/frameless_media_interface_proxy.h b/content/browser/media/frameless_media_interface_proxy.h index bdb094bd..7198bc6 100644 --- a/content/browser/media/frameless_media_interface_proxy.h +++ b/content/browser/media/frameless_media_interface_proxy.h
@@ -26,6 +26,9 @@ // This implements the media::mojom::InterfaceFactory interface for a // RenderProcessHostImpl. It does not support creating services that require a // frame context (ie. CDMs and renderers). +// It is used in cases without a frame context, e.g. WebRTC's +// RTCVideoDecoderFactory to create hardware video decoders using +// MojoVideoDecoder, and WebCodecs audio/video decoding in workers. class CONTENT_EXPORT FramelessMediaInterfaceProxy final : public media::mojom::InterfaceFactory { public: @@ -60,6 +63,12 @@ client_extension, mojo::PendingReceiver<media::mojom::Renderer> receiver) final; #endif // defined(OS_ANDROID) +#if defined(OS_WIN) + void CreateMediaFoundationRenderer( + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) final; +#endif // defined(OS_WIN) void CreateCdm(const std::string& key_system, const media::CdmConfig& cdm_config, CreateCdmCallback callback) final;
diff --git a/content/browser/media/media_interface_proxy.cc b/content/browser/media/media_interface_proxy.cc index 4efe080..f753117 100644 --- a/content/browser/media/media_interface_proxy.cc +++ b/content/browser/media/media_interface_proxy.cc
@@ -450,6 +450,20 @@ } #endif +#if defined(OS_WIN) +void MediaInterfaceProxy::CreateMediaFoundationRenderer( + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) { + DCHECK(thread_checker_.CalledOnValidThread()); + DVLOG(1) << __func__ << ": this=" << this; + + // TODO(frankli): Add code to create MediaFoundationRenderer in the "mf_cdm" + // sandbox process. + NOTIMPLEMENTED(); +} +#endif // defined(OS_WIN) + void MediaInterfaceProxy::CreateCdm(const std::string& key_system, const media::CdmConfig& cdm_config, CreateCdmCallback callback) {
diff --git a/content/browser/media/media_interface_proxy.h b/content/browser/media/media_interface_proxy.h index 905ac9f0..86242a9 100644 --- a/content/browser/media/media_interface_proxy.h +++ b/content/browser/media/media_interface_proxy.h
@@ -76,6 +76,12 @@ mojo::PendingReceiver<media::mojom::MediaPlayerRendererExtension> renderer_extension_request) final; #endif // defined(OS_ANDROID) +#if defined(OS_WIN) + void CreateMediaFoundationRenderer( + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) final; +#endif // defined(OS_WIN) void CreateCdm(const std::string& key_system, const media::CdmConfig& cdm_config, CreateCdmCallback callback) final;
diff --git a/content/browser/network_service_instance_impl.cc b/content/browser/network_service_instance_impl.cc index 02cb492..4baa69f 100644 --- a/content/browser/network_service_instance_impl.cc +++ b/content/browser/network_service_instance_impl.cc
@@ -370,7 +370,7 @@ #if defined(OS_WIN) // base::Environment returns environment variables in UTF-8 on // Windows. - ssl_key_log_path = base::FilePath(base::UTF8ToUTF16(env_str)); + ssl_key_log_path = base::FilePath(base::UTF8ToWide(env_str)); #else ssl_key_log_path = base::FilePath(env_str); #endif
diff --git a/content/browser/ppapi_plugin_process_host.cc b/content/browser/ppapi_plugin_process_host.cc index ae0a8f3..4ee1f46e 100644 --- a/content/browser/ppapi_plugin_process_host.cc +++ b/content/browser/ppapi_plugin_process_host.cc
@@ -92,7 +92,7 @@ return false; } #endif // !defined(NACL_WIN64) - const base::string16& sid = + const std::wstring& sid = browser_client->GetAppContainerSidForSandboxType(GetSandboxType()); if (!sid.empty()) sandbox::policy::SandboxWin::AddAppContainerPolicy(policy, sid.c_str());
diff --git a/content/browser/renderer_host/dwrite_font_file_util_win.cc b/content/browser/renderer_host/dwrite_font_file_util_win.cc index b998954..6958f633 100644 --- a/content/browser/renderer_host/dwrite_font_file_util_win.cc +++ b/content/browser/renderer_host/dwrite_font_file_util_win.cc
@@ -12,13 +12,14 @@ #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "base/trace_event/trace_event.h" #include "content/browser/renderer_host/dwrite_font_uma_logging_win.h" namespace content { HRESULT FontFilePathAndTtcIndex(IDWriteFont* font, - base::string16& file_path, + std::wstring& file_path, uint32_t& ttc_index) { Microsoft::WRL::ComPtr<IDWriteFontFace> font_face; HRESULT hr; @@ -34,7 +35,7 @@ } HRESULT FontFilePathAndTtcIndex(IDWriteFontFace* font_face, - base::string16& file_path, + std::wstring& file_path, uint32_t& ttc_index) { TRACE_EVENT0("dwrite,fonts", "dwrite_font_file_util::FontFilePathAndTtcIndex"); @@ -114,7 +115,7 @@ MessageFilterError::ADD_LOCAL_FILE_GET_PATH_LENGTH_FAILED); return hr; } - base::string16 retrieve_file_path; + std::wstring retrieve_file_path; retrieve_file_path.resize( ++path_length); // Reserve space for the null terminator. hr = local_loader->GetFilePathFromKey(key, key_size, &retrieve_file_path[0], @@ -139,16 +140,17 @@ HRESULT AddFilesForFont(IDWriteFont* font, const base::string16& windows_fonts_path, - std::set<base::string16>* path_set, - std::set<base::string16>* custom_font_path_set, + std::set<std::wstring>* path_set, + std::set<std::wstring>* custom_font_path_set, uint32_t* ttc_index) { - base::string16 file_path; + std::wstring file_path; HRESULT hr = FontFilePathAndTtcIndex(font, file_path, *ttc_index); if (FAILED(hr)) { return hr; } - base::string16 file_path_folded = base::i18n::FoldCase(file_path); + base::string16 file_path_folded = + base::i18n::FoldCase(base::WideToUTF16(file_path)); if (!file_path_folded.size()) return kErrorFontFileUtilEmptyFilePath; @@ -165,14 +167,14 @@ } base::string16 GetWindowsFontsPath() { - std::vector<base::char16> font_path_chars; + std::vector<wchar_t> font_path_chars; // SHGetSpecialFolderPath requires at least MAX_PATH characters. font_path_chars.resize(MAX_PATH); BOOL result = SHGetSpecialFolderPath(nullptr /* hwndOwner - reserved */, font_path_chars.data(), CSIDL_FONTS, FALSE /* fCreate */); DCHECK(result); - return base::i18n::FoldCase(font_path_chars.data()); + return base::i18n::FoldCase(base::AsStringPiece16(font_path_chars.data())); } } // namespace content
diff --git a/content/browser/renderer_host/dwrite_font_file_util_win.h b/content/browser/renderer_host/dwrite_font_file_util_win.h index 4732144..809808c 100644 --- a/content/browser/renderer_host/dwrite_font_file_util_win.h +++ b/content/browser/renderer_host/dwrite_font_file_util_win.h
@@ -30,8 +30,8 @@ uint32_t& ttc_index); HRESULT AddFilesForFont(IDWriteFont* font, const base::string16& windows_fonts_path, - std::set<base::string16>* path_set, - std::set<base::string16>* custom_font_path_set, + std::set<std::wstring>* path_set, + std::set<std::wstring>* custom_font_path_set, uint32_t* ttc_index); base::string16 GetWindowsFontsPath();
diff --git a/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc b/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc index b468671..3b3913c 100644 --- a/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc +++ b/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc
@@ -96,7 +96,7 @@ // https://dxr.mozilla.org/mozilla-central/source/gfx/thebes/gfxDWriteFontList.cpp#90 // so we'll assume that. localized_strings->push_back(base::UTF16ToUTF8( - base::i18n::FoldCase(base::string16(localized_name)))); + base::i18n::FoldCase(base::WideToUTF16(localized_name)))); } return true; } @@ -206,7 +206,7 @@ DCHECK(dwrite_version_info); std::string dwrite_version = - base::WideToUTF8(dwrite_version_info->product_version()); + base::UTF16ToUTF8(dwrite_version_info->product_version()); std::string to_hash = dwrite_version; @@ -490,8 +490,8 @@ if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) continue; - std::set<base::string16> path_set; - std::set<base::string16> custom_font_path_set; + std::set<std::wstring> path_set; + std::set<std::wstring> custom_font_path_set; uint32_t ttc_index = 0; { base::ScopedBlockingCall scoped_blocking_call(
diff --git a/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc b/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc index 7f1710b..d0c33c6f 100644 --- a/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc +++ b/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc
@@ -52,7 +52,7 @@ L"Segoe UI", L"Calibri", L"Times New Roman", L"Courier New"}; struct RequiredFontStyle { - const wchar_t* family_name; + const base::char16* family_name; DWRITE_FONT_WEIGHT required_weight; DWRITE_FONT_STRETCH required_stretch; DWRITE_FONT_STYLE required_style; @@ -61,12 +61,12 @@ const RequiredFontStyle kRequiredStyles[] = { // The regular version of Gill Sans is actually in the Gill Sans MT family, // and the Gill Sans family typically contains just the ultra-bold styles. - {L"gill sans", DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, - DWRITE_FONT_STYLE_NORMAL}, - {L"helvetica", DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, - DWRITE_FONT_STYLE_NORMAL}, - {L"open sans", DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, - DWRITE_FONT_STYLE_NORMAL}, + {STRING16_LITERAL("gill sans"), DWRITE_FONT_WEIGHT_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL}, + {STRING16_LITERAL("helvetica"), DWRITE_FONT_WEIGHT_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL}, + {STRING16_LITERAL("open sans"), DWRITE_FONT_WEIGHT_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL}, }; // As a workaround for crbug.com/635932, refuse to load some common fonts that @@ -137,8 +137,8 @@ if (collection_) { BOOL exists = FALSE; UINT32 index = UINT32_MAX; - HRESULT hr = - collection_->FindFamilyName(family_name.data(), &index, &exists); + HRESULT hr = collection_->FindFamilyName(base::as_wcstr(family_name), + &index, &exists); if (SUCCEEDED(hr) && exists && CheckRequiredStylesPresent(collection_.Get(), family_name, index)) { family_index = index; @@ -178,8 +178,8 @@ size_t string_count = localized_names->GetCount(); - std::vector<base::char16> locale; - std::vector<base::char16> name; + std::vector<wchar_t> locale; + std::vector<wchar_t> name; std::vector<blink::mojom::DWriteStringPairPtr> family_names; for (size_t index = 0; index < string_count; ++index) { UINT32 length = 0; @@ -208,8 +208,8 @@ } CHECK_EQ(L'\0', name[length - 1]); - family_names.emplace_back(base::in_place, base::string16(locale.data()), - base::string16(name.data())); + family_names.emplace_back(base::in_place, base::WideToUTF16(locale.data()), + base::WideToUTF16(name.data())); } std::move(callback).Run(std::move(family_names)); } @@ -235,8 +235,8 @@ UINT32 font_count = family->GetFontCount(); - std::set<base::string16> path_set; - std::set<base::string16> custom_font_path_set; + std::set<std::wstring> path_set; + std::set<std::wstring> custom_font_path_set; // Iterate through all the fonts in the family, and all the files for those // fonts. If anything goes wrong, bail on the entire family to avoid having // a partially-loaded font family. @@ -264,7 +264,7 @@ // as file handles. The renderer would be unable to open the files directly // due to sandbox policy (it would get ERROR_ACCESS_DENIED instead). Passing // handles allows the renderer to bypass the restriction and use the fonts. - for (const base::string16& custom_font_path : custom_font_path_set) { + for (const auto& custom_font_path : custom_font_path_set) { // Specify FLAG_EXCLUSIVE_WRITE to prevent base::File from opening the file // with FILE_SHARE_WRITE access. FLAG_EXCLUSIVE_WRITE doesn't actually open // the file for write access. @@ -311,14 +311,15 @@ mswr::ComPtr<IDWriteNumberSubstitution> number_substitution; if (FAILED(factory2_->CreateNumberSubstitution( - DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, locale_name.c_str(), + DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, base::as_wcstr(locale_name), TRUE /* ignoreUserOverride */, &number_substitution))) { DCHECK(false); return; } mswr::ComPtr<IDWriteTextAnalysisSource> analysis_source; if (FAILED(gfx::win::TextAnalysisSource::Create( - &analysis_source, text, locale_name, number_substitution.Get(), + &analysis_source, base::UTF16ToWide(text), + base::UTF16ToWide(locale_name), number_substitution.Get(), static_cast<DWRITE_READING_DIRECTION>(reading_direction)))) { DCHECK(false); return; @@ -331,7 +332,7 @@ DWRITE_FONT_WEIGHT_NORMAL)); if (FAILED(font_fallback_->MapCharacters( analysis_source.Get(), 0, text.length(), collection_.Get(), - base_family_name.c_str(), + base::as_wcstr(base_family_name), static_cast<DWRITE_FONT_WEIGHT>(font_style->font_weight), static_cast<DWRITE_FONT_STYLE>(font_style->font_slant), static_cast<DWRITE_FONT_STRETCH>(font_style->font_stretch), @@ -360,7 +361,7 @@ result->font_style->font_stretch = mapped_font->GetStretch(); result->font_style->font_weight = mapped_font->GetWeight(); - std::vector<base::char16> name; + std::vector<wchar_t> name; size_t name_count = family_names->GetCount(); for (size_t name_index = 0; name_index < name_count; name_index++) { UINT32 name_length = 0;
diff --git a/content/browser/renderer_host/media/media_stream_ui_proxy.cc b/content/browser/renderer_host/media/media_stream_ui_proxy.cc index 28adfe5..d396351 100644 --- a/content/browser/renderer_host/media/media_stream_ui_proxy.cc +++ b/content/browser/renderer_host/media/media_stream_ui_proxy.cc
@@ -99,6 +99,10 @@ // cancel media requests. base::WeakPtrFactory<Core> weak_factory_{this}; + // Used for calls supplied to `ui_`. Invalidated every time a new UI is + // created. + base::WeakPtrFactory<Core> weak_factory_for_ui_{this}; + DISALLOW_COPY_AND_ASSIGN(Core); }; @@ -144,20 +148,22 @@ std::vector<DesktopMediaID> screen_share_ids) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - // base::Unretained is safe here because |ui_| is owned by Core. + if (!ui_) + return; + MediaStreamUI::SourceCallback device_change_cb; if (has_source_callback) { - device_change_cb = base::BindRepeating( - &Core::ProcessChangeSourceRequestFromUI, base::Unretained(this)); + device_change_cb = + base::BindRepeating(&Core::ProcessChangeSourceRequestFromUI, + weak_factory_for_ui_.GetWeakPtr()); } - if (ui_) { - *window_id = ui_->OnStarted( - base::BindOnce(&Core::ProcessStopRequestFromUI, base::Unretained(this)), - device_change_cb, label, screen_share_ids, - base::BindRepeating(&Core::ProcessStateChangeFromUI, - base::Unretained(this))); - } + *window_id = + ui_->OnStarted(base::BindOnce(&Core::ProcessStopRequestFromUI, + weak_factory_for_ui_.GetWeakPtr()), + device_change_cb, label, screen_share_ids, + base::BindRepeating(&Core::ProcessStateChangeFromUI, + weak_factory_for_ui_.GetWeakPtr())); } void MediaStreamUIProxy::Core::OnDeviceStopped(const std::string& label, @@ -197,13 +203,12 @@ result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED; if (stream_ui) { - // Default TabSharingUIViews always calls stop callback when destroyed - // due to security reasons (see crbug.com/1155426 for details). - // However here the UI is replaced while the screencast is ongoing. - // Clearing the callback here ensures that screencast is not terminated. - if (ui_) { - ui_->SetStopCallback(base::DoNothing()); - } + // Callbacks that were supplied to the existing `ui_` are no longer + // applicable. This is important as some implementions (TabSharingUIViews) + // always run the callback when destroyed. However at the point the UI is + // replaced while the screencast is ongoing. Invalidating ensures the + // screencast is not terminated. See crbug.com/1155426 for details. + weak_factory_for_ui_.InvalidateWeakPtrs(); ui_ = std::move(stream_ui); }
diff --git a/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc b/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc index 8041fbb..abe6bf3 100644 --- a/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc +++ b/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc
@@ -81,7 +81,6 @@ MOCK_METHOD2(MockOnStarted, gfx::NativeViewId(base::RepeatingClosure stop, MediaStreamUI::SourceCallback source)); - MOCK_METHOD1(SetStopCallback, void(base::OnceClosure)); }; class MockStopStreamHandler {
diff --git a/content/browser/renderer_host/pepper/pepper_file_io_host.cc b/content/browser/renderer_host/pepper/pepper_file_io_host.cc index 90caac4..dbf0c9d7 100644 --- a/content/browser/renderer_host/pepper/pepper_file_io_host.cc +++ b/content/browser/renderer_host/pepper/pepper_file_io_host.cc
@@ -194,7 +194,7 @@ // Whitelist the supported ones. if (file_system_url_.mount_type() == storage::kFileSystemTypeExternal) { switch (file_system_url_.type()) { - case storage::kFileSystemTypeNativeMedia: + case storage::kFileSystemTypeLocalMedia: case storage::kFileSystemTypeDeviceMedia: break; default:
diff --git a/content/browser/renderer_host/render_frame_host_android.cc b/content/browser/renderer_host/render_frame_host_android.cc index cb08d98..f9b3317b 100644 --- a/content/browser/renderer_host/render_frame_host_android.cc +++ b/content/browser/renderer_host/render_frame_host_android.cc
@@ -11,13 +11,14 @@ #include "base/android/unguessable_token_android.h" #include "base/bind.h" #include "base/check_op.h" +#include "content/browser/bad_message.h" #include "content/browser/renderer_host/render_frame_host_delegate.h" #include "content/browser/renderer_host/render_frame_host_impl.h" #include "content/public/android/content_jni_headers/RenderFrameHostImpl_jni.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/site_instance.h" -#include "mojo/public/cpp/bindings/pending_remote.h" +#include "services/service_manager/public/cpp/interface_provider.h" #include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-shared.h" #include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom.h" #include "url/origin.h" @@ -61,11 +62,8 @@ } RenderFrameHostAndroid::RenderFrameHostAndroid( - RenderFrameHostImpl* render_frame_host, - mojo::PendingRemote<service_manager::mojom::InterfaceProvider> - interface_provider_remote) - : render_frame_host_(render_frame_host), - interface_provider_remote_(std::move(interface_provider_remote)) {} + RenderFrameHostImpl* render_frame_host) + : render_frame_host_(render_frame_host) {} RenderFrameHostAndroid::~RenderFrameHostAndroid() { // Avoid unnecessarily creating the java object from the destructor. @@ -89,7 +87,7 @@ ScopedJavaLocalRef<jobject> local_ref = Java_RenderFrameHostImpl_create( env, reinterpret_cast<intptr_t>(this), render_frame_host_->delegate()->GetJavaRenderFrameHostDelegate(), - is_incognito, interface_provider_remote_.PassPipe().release().value()); + is_incognito); obj_ = JavaObjectWeakGlobalRef(env, local_ref); return local_ref; } @@ -147,6 +145,27 @@ return render_frame_host_->IsRenderFrameCreated(); } +void RenderFrameHostAndroid::GetInterfaceToRendererFrame( + JNIEnv* env, + const base::android::JavaParamRef<jobject>&, + const base::android::JavaParamRef<jstring>& interface_name, + jint message_pipe_raw_handle) const { + DCHECK(render_frame_host_->IsRenderFrameCreated()); + render_frame_host_->GetRemoteInterfaces()->GetInterfaceByName( + ConvertJavaStringToUTF8(env, interface_name), + mojo::ScopedMessagePipeHandle( + mojo::MessagePipeHandle(message_pipe_raw_handle))); +} + +void RenderFrameHostAndroid::TerminateRendererDueToBadMessage( + JNIEnv* env, + const base::android::JavaParamRef<jobject>&, + jint reason) const { + DCHECK_LT(reason, bad_message::BAD_MESSAGE_MAX); + ReceivedBadMessage(render_frame_host_->GetProcess(), + static_cast<bad_message::BadMessageReason>(reason)); +} + jboolean RenderFrameHostAndroid::IsProcessBlocked( JNIEnv* env, const base::android::JavaParamRef<jobject>&) const {
diff --git a/content/browser/renderer_host/render_frame_host_android.h b/content/browser/renderer_host/render_frame_host_android.h index 3dbbe73..7b70f6b 100644 --- a/content/browser/renderer_host/render_frame_host_android.h +++ b/content/browser/renderer_host/render_frame_host_android.h
@@ -16,8 +16,6 @@ #include "base/macros.h" #include "base/supports_user_data.h" #include "content/common/content_export.h" -#include "mojo/public/cpp/bindings/pending_remote.h" -#include "services/service_manager/public/mojom/interface_provider.mojom.h" namespace content { @@ -28,10 +26,7 @@ // native counterpart. class RenderFrameHostAndroid : public base::SupportsUserData::Data { public: - RenderFrameHostAndroid( - RenderFrameHostImpl* render_frame_host, - mojo::PendingRemote<service_manager::mojom::InterfaceProvider> - interface_provider_remote); + RenderFrameHostAndroid(RenderFrameHostImpl* render_frame_host); ~RenderFrameHostAndroid() override; base::android::ScopedJavaLocalRef<jobject> GetJavaObject(); @@ -66,6 +61,17 @@ JNIEnv* env, const base::android::JavaParamRef<jobject>&) const; + void GetInterfaceToRendererFrame( + JNIEnv* env, + const base::android::JavaParamRef<jobject>&, + const base::android::JavaParamRef<jstring>& interface_name, + jint message_pipe_handle) const; + + void TerminateRendererDueToBadMessage( + JNIEnv* env, + const base::android::JavaParamRef<jobject>&, + jint reason) const; + jboolean IsProcessBlocked(JNIEnv* env, const base::android::JavaParamRef<jobject>&) const; @@ -85,8 +91,6 @@ private: RenderFrameHostImpl* const render_frame_host_; - mojo::PendingRemote<service_manager::mojom::InterfaceProvider> - interface_provider_remote_; JavaObjectWeakGlobalRef obj_; DISALLOW_COPY_AND_ASSIGN(RenderFrameHostAndroid);
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index 0365c6c..0300a4fa 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -8503,48 +8503,13 @@ } #if defined(OS_ANDROID) - -class RenderFrameHostImpl::JavaInterfaceProvider - : public service_manager::mojom::InterfaceProvider { - public: - using BindCallback = - base::RepeatingCallback<void(const std::string&, - mojo::ScopedMessagePipeHandle)>; - - JavaInterfaceProvider( - const BindCallback& bind_callback, - mojo::PendingReceiver<service_manager::mojom::InterfaceProvider> receiver) - : bind_callback_(bind_callback), receiver_(this, std::move(receiver)) {} - ~JavaInterfaceProvider() override = default; - - private: - // service_manager::mojom::InterfaceProvider: - void GetInterface(const std::string& interface_name, - mojo::ScopedMessagePipeHandle handle) override { - bind_callback_.Run(interface_name, std::move(handle)); - } - - const BindCallback bind_callback_; - mojo::Receiver<service_manager::mojom::InterfaceProvider> receiver_; - - DISALLOW_COPY_AND_ASSIGN(JavaInterfaceProvider); -}; - base::android::ScopedJavaLocalRef<jobject> RenderFrameHostImpl::GetJavaRenderFrameHost() { RenderFrameHostAndroid* render_frame_host_android = static_cast<RenderFrameHostAndroid*>( GetUserData(kRenderFrameHostAndroidKey)); if (!render_frame_host_android) { - mojo::PendingRemote<service_manager::mojom::InterfaceProvider> - interface_provider_remote; - java_interface_registry_ = std::make_unique<JavaInterfaceProvider>( - base::BindRepeating( - &RenderFrameHostImpl::ForwardGetInterfaceToRenderFrame, - weak_ptr_factory_.GetWeakPtr()), - interface_provider_remote.InitWithNewPipeAndPassReceiver()); - render_frame_host_android = - new RenderFrameHostAndroid(this, std::move(interface_provider_remote)); + render_frame_host_android = new RenderFrameHostAndroid(this); SetUserData(kRenderFrameHostAndroidKey, base::WrapUnique(render_frame_host_android)); } @@ -8562,12 +8527,6 @@ } return java_interfaces_.get(); } - -void RenderFrameHostImpl::ForwardGetInterfaceToRenderFrame( - const std::string& interface_name, - mojo::ScopedMessagePipeHandle pipe) { - GetRemoteInterfaces()->GetInterfaceByName(interface_name, std::move(pipe)); -} #endif void RenderFrameHostImpl::ForEachImmediateLocalRoot(
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h index 2330ad2..ccbfe9e 100644 --- a/content/browser/renderer_host/render_frame_host_impl.h +++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -2105,11 +2105,6 @@ const url::Origin& frame_origin, net::IsolationInfo::RequestType request_type) const; -#if defined(OS_ANDROID) - void ForwardGetInterfaceToRenderFrame(const std::string& interface_name, - mojo::ScopedMessagePipeHandle pipe); -#endif - // mojom::FrameHost: void CreateNewWindow(mojom::CreateNewWindowParamsPtr params, CreateNewWindowCallback callback) override; @@ -3121,12 +3116,6 @@ // this RenderFrameHost. This provides access to interfaces implemented in // Java in the browser process to C++ code in the browser process. std::unique_ptr<service_manager::InterfaceProvider> java_interfaces_; - - // An InterfaceRegistry that forwards interface requests from Java to the - // RenderFrame. This provides access to interfaces implemented in the renderer - // to Java code in the browser process. - class JavaInterfaceProvider; - std::unique_ptr<JavaInterfaceProvider> java_interface_registry_; #endif // BrowserInterfaceBroker implementation through which this
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index c31df2f..347adca 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -411,7 +411,7 @@ bool PreSpawnTarget(sandbox::TargetPolicy* policy) override { sandbox::policy::SandboxWin::AddBaseHandleClosePolicy(policy); - const base::string16& sid = + const std::wstring& sid = GetContentClient()->browser()->GetAppContainerSidForSandboxType( GetSandboxType()); if (!sid.empty())
diff --git a/content/browser/speech/tts_win.cc b/content/browser/speech/tts_win.cc index 80d9e56..e41cab82 100644 --- a/content/browser/speech/tts_win.cc +++ b/content/browser/speech/tts_win.cc
@@ -262,12 +262,12 @@ // The TTS api allows a range of -10 to 10 for speech pitch: // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms720500(v%3Dvs.85) // Note that the API requires an integer value, so be sure to cast the pitch - // value to an int before calling NumberToString16. TODO(dtseng): cleanup if + // 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)); std::wstring adjusted_pitch_string = - base::NumberToString16(static_cast<int>(adjusted_pitch)); + base::NumberToWString(static_cast<int>(adjusted_pitch)); prefix = L"<pitch absmiddle=\"" + adjusted_pitch_string + L"\">"; suffix = L"</pitch>"; }
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc index 50bec12b..e3628104 100644 --- a/content/browser/storage_partition_impl.cc +++ b/content/browser/storage_partition_impl.cc
@@ -1222,7 +1222,8 @@ cache_storage_context_ = base::MakeRefCounted<CacheStorageContextImpl>(); cache_storage_context_->Init( - path, browser_context_->GetSpecialStoragePolicy(), quota_manager_proxy); + path, browser_context_->GetSpecialStoragePolicy(), quota_manager_proxy, + ChromeBlobStorageContext::GetRemoteFor(browser_context_)); cache_storage_context_->Bind( cache_storage_control_.BindNewPipeAndPassReceiver());
diff --git a/content/browser/storage_partition_impl_map.cc b/content/browser/storage_partition_impl_map.cc index 865eeae4..e7eee25 100644 --- a/content/browser/storage_partition_impl_map.cc +++ b/content/browser/storage_partition_impl_map.cc
@@ -469,9 +469,6 @@ // Check first to avoid memory leak in unittests. if (BrowserThread::IsThreadInitialized(BrowserThread::IO)) { - partition->GetCacheStorageContext()->SetBlobParametersForCache( - ChromeBlobStorageContext::GetFor(browser_context_)); - // 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
diff --git a/content/browser/tracing/tracing_controller_impl.cc b/content/browser/tracing/tracing_controller_impl.cc index 4f938ad..4d2ee92 100644 --- a/content/browser/tracing/tracing_controller_impl.cc +++ b/content/browser/tracing/tracing_controller_impl.cc
@@ -19,6 +19,7 @@ #include "base/run_loop.h" #include "base/strings/string_split.h" #include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" #include "base/system/sys_info.h" #include "base/time/time.h" #include "base/trace_event/trace_config.h" @@ -340,9 +341,13 @@ metadata_dict->SetBoolean("highres-ticks", base::TimeTicks::IsHighResolution()); - metadata_dict->SetString( - "command_line", - base::CommandLine::ForCurrentProcess()->GetCommandLineString()); + base::CommandLine::StringType command_line = + base::CommandLine::ForCurrentProcess()->GetCommandLineString(); +#if defined(OS_WIN) + metadata_dict->SetString("command_line", base::WideToUTF16(command_line)); +#else + metadata_dict->SetString("command_line", command_line); +#endif base::Time::Exploded ctime; TRACE_TIME_NOW().UTCExplode(&ctime);
diff --git a/content/browser/web_contents/web_contents_view_aura.cc b/content/browser/web_contents/web_contents_view_aura.cc index 6c527e2..4bdcc50 100644 --- a/content/browser/web_contents/web_contents_view_aura.cc +++ b/content/browser/web_contents/web_contents_view_aura.cc
@@ -190,12 +190,9 @@ std::string default_name = GetContentClient()->browser()->GetDefaultDownloadName(); base::FilePath generated_download_file_name = - net::GenerateFileName(download_url, - std::string(), - std::string(), - base::UTF16ToUTF8(file_name.value()), - base::UTF16ToUTF8(mime_type), - default_name); + net::GenerateFileName(download_url, std::string(), std::string(), + base::WideToUTF8(file_name.value()), + base::UTF16ToUTF8(mime_type), default_name); // http://crbug.com/332579 base::ThreadRestrictions::ScopedAllowIO allow_file_operations;
diff --git a/content/browser/xr/service/vr_service_impl.cc b/content/browser/xr/service/vr_service_impl.cc index 2129e3f1..75346867 100644 --- a/content/browser/xr/service/vr_service_impl.cc +++ b/content/browser/xr/service/vr_service_impl.cc
@@ -571,6 +571,8 @@ } } + runtime_options->depth_options = std::move(request.options->depth_options); + base::OnceCallback<void(device::mojom::XRSessionPtr)> immersive_callback = base::BindOnce(&VRServiceImpl::OnImmersiveSessionCreated, weak_ptr_factory_.GetWeakPtr(), std::move(request));
diff --git a/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc b/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc index 4054c9d5..4ae2c25 100644 --- a/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc +++ b/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc
@@ -46,7 +46,7 @@ L"Sans", L"Arial", L"MS UI Gothic", L"Microsoft Sans Serif", L"Segoe UI", L"Calibri", L"Times New Roman", L"Courier New"}; -bool IsLastResortFontName(const base::string16& font_name) { +bool IsLastResortFontName(const std::wstring& font_name) { for (const wchar_t* last_resort_font_name : kLastResortFontNames) { if (font_name == last_resort_font_name) return true; @@ -127,7 +127,7 @@ DCHECK(exists); TRACE_EVENT0("dwrite,fonts", "FontProxy::FindFamilyName"); - base::string16 name(family_name); + std::wstring name(family_name); auto iter = family_names_.find(name); if (iter != family_names_.end()) { @@ -347,7 +347,7 @@ mswr::ComPtr<DWriteFontFamilyProxy>& family = families_[family_index]; if (!family->IsLoaded() || family->GetName().empty()) - family->SetName(family_name); + family->SetName(base::UTF16ToWide(family_name)); family.CopyTo(font_family); return true; @@ -522,11 +522,11 @@ return SUCCEEDED(hr); } -void DWriteFontFamilyProxy::SetName(const base::string16& family_name) { +void DWriteFontFamilyProxy::SetName(const std::wstring& family_name) { family_name_.assign(family_name); } -const base::string16& DWriteFontFamilyProxy::GetName() { +const std::wstring& DWriteFontFamilyProxy::GetName() { return family_name_; }
diff --git a/content/child/dwrite_font_proxy/dwrite_font_proxy_win.h b/content/child/dwrite_font_proxy/dwrite_font_proxy_win.h index 0e14fa0..2e9619b 100644 --- a/content/child/dwrite_font_proxy/dwrite_font_proxy_win.h +++ b/content/child/dwrite_font_proxy/dwrite_font_proxy_win.h
@@ -104,7 +104,7 @@ private: Microsoft::WRL::ComPtr<IDWriteFactory> factory_; std::vector<Microsoft::WRL::ComPtr<DWriteFontFamilyProxy>> families_; - std::map<base::string16, UINT32> family_names_; + std::map<std::wstring, UINT32> family_names_; UINT32 family_count_ = UINT_MAX; scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; // Per-sequence mojo::Remote<DWriteFontProxy>. This is preferred to a @@ -151,9 +151,9 @@ bool GetFontFromFontFace(IDWriteFontFace* font_face, IDWriteFont** font); - void SetName(const base::string16& family_name); + void SetName(const std::wstring& family_name); - const base::string16& GetName(); + const std::wstring& GetName(); bool IsLoaded(); @@ -162,7 +162,7 @@ private: UINT32 family_index_; - base::string16 family_name_; + std::wstring family_name_; Microsoft::WRL::ComPtr<DWriteFontCollectionProxy> proxy_collection_; Microsoft::WRL::ComPtr<IDWriteFontFamily> family_; Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> family_names_;
diff --git a/content/child/dwrite_font_proxy/dwrite_localized_strings_win.cc b/content/child/dwrite_font_proxy/dwrite_localized_strings_win.cc index 987c2c2..87a0efd 100644 --- a/content/child/dwrite_font_proxy/dwrite_localized_strings_win.cc +++ b/content/child/dwrite_font_proxy/dwrite_localized_strings_win.cc
@@ -38,7 +38,7 @@ UINT32 size) { if (index >= strings_.size()) return E_INVALIDARG; - // string16::size does not count the null terminator as part of the string, + // wstring::size does not count the null terminator as part of the string, // but GetLocaleName requires the caller to reserve space for the null // terminator, so we need to ensure |size| is greater than the count of // characters. @@ -64,7 +64,7 @@ UINT32 size) { if (index >= strings_.size()) return E_INVALIDARG; - // string16::size does not count the null terminator as part of the string, + // wstring::size does not count the null terminator as part of the string, // but GetString requires the caller to reserve space for the null terminator, // so we need to ensure |size| is greater than the count of characters. if (size <= strings_[index].second.size()) @@ -84,7 +84,7 @@ } HRESULT DWriteLocalizedStrings::RuntimeClassInitialize( - std::vector<std::pair<base::string16, base::string16>>* strings) { + std::vector<std::pair<std::wstring, std::wstring>>* strings) { strings_.swap(*strings); return S_OK; }
diff --git a/content/child/dwrite_font_proxy/dwrite_localized_strings_win.h b/content/child/dwrite_font_proxy/dwrite_localized_strings_win.h index 796f435..0b7a0266 100644 --- a/content/child/dwrite_font_proxy/dwrite_localized_strings_win.h +++ b/content/child/dwrite_font_proxy/dwrite_localized_strings_win.h
@@ -7,11 +7,11 @@ #include <dwrite.h> #include <wrl.h> +#include <string> #include <utility> #include <vector> #include "base/macros.h" -#include "base/strings/string16.h" namespace content { @@ -41,14 +41,14 @@ UINT32* length) override; HRESULT STDMETHODCALLTYPE RuntimeClassInitialize( - std::vector<std::pair<base::string16, base::string16>>* strings); + std::vector<std::pair<std::wstring, std::wstring>>* strings); private: // List of strings. First element of each pair is the locale, and the second // element is the associated value. Use a vector because the expected number // of pairs is small (typically 1-2, rarely up to a few dozen?) and we need // index-based access. - std::vector<std::pair<base::string16, base::string16>> strings_; + std::vector<std::pair<std::wstring, std::wstring>> strings_; DISALLOW_ASSIGN(DWriteLocalizedStrings); };
diff --git a/content/child/dwrite_font_proxy/font_fallback_win.cc b/content/child/dwrite_font_proxy/font_fallback_win.cc index 8afb113..8532bc4 100644 --- a/content/child/dwrite_font_proxy/font_fallback_win.cc +++ b/content/child/dwrite_font_proxy/font_fallback_win.cc
@@ -11,6 +11,7 @@ #include "base/metrics/histogram_macros.h" #include "base/strings/string16.h" #include "base/strings/utf_string_conversion_utils.h" +#include "base/strings/utf_string_conversions.h" #include "base/trace_event/trace_event.h" #include "content/child/dwrite_font_proxy/dwrite_font_proxy_win.h" @@ -39,9 +40,9 @@ fallback_result, FONT_FALLBACK_RESULT_MAX_VALUE); } -base::string16 MakeCacheKey(const wchar_t* base_family_name, - const wchar_t* locale) { - base::string16 cache_key(base_family_name); +std::wstring MakeCacheKey(const wchar_t* base_family_name, + const wchar_t* locale) { + std::wstring cache_key(base_family_name); return cache_key + L"_" + locale; } @@ -77,7 +78,8 @@ DCHECK(false); return E_FAIL; } - base::string16 text_chunk(text, std::min(chunk_length, text_length)); + base::string16 text_chunk; + base::WideToUTF16(text, std::min(chunk_length, text_length), &text_chunk); if (text_chunk.size() == 0) { DCHECK(false); @@ -107,12 +109,12 @@ blink::mojom::MapCharactersResultPtr result; - if (!GetFontProxy().MapCharacters(text_chunk, - blink::mojom::DWriteFontStyle::New( - base_weight, base_style, base_stretch), - locale, - source->GetParagraphReadingDirection(), - base_family_name, &result)) { + if (!GetFontProxy().MapCharacters( + text_chunk, + blink::mojom::DWriteFontStyle::New(base_weight, base_style, + base_stretch), + base::WideToUTF16(locale), source->GetParagraphReadingDirection(), + base::WideToUTF16(base_family_name), &result)) { DCHECK(false); return E_FAIL; }
diff --git a/content/child/dwrite_font_proxy/font_fallback_win.h b/content/child/dwrite_font_proxy/font_fallback_win.h index b6f604a..623f76f1 100644 --- a/content/child/dwrite_font_proxy/font_fallback_win.h +++ b/content/child/dwrite_font_proxy/font_fallback_win.h
@@ -75,7 +75,7 @@ // of font families that matched a character on a previous call. The list is // capped in size and maintained in MRU order. This gives us a good chance of // returning a suitable fallback font without having to do an IPC. - std::map<base::string16, std::list<Microsoft::WRL::ComPtr<IDWriteFontFamily>>> + std::map<std::wstring, std::list<Microsoft::WRL::ComPtr<IDWriteFontFamily>>> fallback_family_cache_; DISALLOW_ASSIGN(FontFallback);
diff --git a/content/common/content_navigation_policy.cc b/content/common/content_navigation_policy.cc index a05e328..0391d599 100644 --- a/content/common/content_navigation_policy.cc +++ b/content/common/content_navigation_policy.cc
@@ -9,6 +9,7 @@ #include "base/command_line.h" #include "base/metrics/field_trial_params.h" #include "base/system/sys_info.h" +#include "build/build_config.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" @@ -21,9 +22,21 @@ // It is important to check the base::FeatureList to avoid activating any // field trial groups if BFCache is disabled due to memory threshold. if (base::FeatureList::IsEnabled(features::kBackForwardCacheMemoryControl)) { + // On Android, BackForwardCache is only enabled for 2GB+ high memory + // devices. The default threshold value is set to 1700 MB to account for all + // 2GB devices which report lower RAM due to carveouts. + int default_memory_threshold_mb = +#if defined(OS_ANDROID) + 1700; +#else + // Desktop has lower memory limitations compared to Android allowing us + // to enable BackForwardCache for all devices. + 0; +#endif int memory_threshold_mb = base::GetFieldTrialParamByFeatureAsInt( features::kBackForwardCacheMemoryControl, - "memory_threshold_for_back_forward_cache_in_mb", 0); + "memory_threshold_for_back_forward_cache_in_mb", + default_memory_threshold_mb); return base::SysInfo::AmountOfPhysicalMemoryMB() > memory_threshold_mb; }
diff --git a/content/common/content_switches_internal.cc b/content/common/content_switches_internal.cc index 2dfb2310..95eacdb 100644 --- a/content/common/content_switches_internal.cc +++ b/content/common/content_switches_internal.cc
@@ -40,12 +40,12 @@ #if defined(OS_WIN) -base::string16 ToNativeString(base::StringPiece string) { - return base::ASCIIToUTF16(string); +std::wstring ToNativeString(base::StringPiece string) { + return base::ASCIIToWide(string); } -std::string FromNativeString(base::StringPiece16 string) { - return base::UTF16ToASCII(string); +std::string FromNativeString(base::WStringPiece string) { + return base::WideToASCII(string); } #else // defined(OS_WIN)
diff --git a/content/common/font_cache_dispatcher_win.cc b/content/common/font_cache_dispatcher_win.cc index 8a75ec4..9418212e 100644 --- a/content/common/font_cache_dispatcher_win.cc +++ b/content/common/font_cache_dispatcher_win.cc
@@ -19,7 +19,7 @@ namespace content { namespace { -typedef std::vector<base::string16> FontNameVector; +typedef std::vector<std::wstring> FontNameVector; typedef std::map<FontCacheDispatcher*, FontNameVector> DispatcherToFontNames; class FontCache { @@ -43,7 +43,7 @@ BOOL ret = GetTextMetrics(hdc, &tm); DCHECK(ret); - base::string16 font_name = font.lfFaceName; + std::wstring font_name = font.lfFaceName; bool inc_ref_count = true; if (!base::Contains(dispatcher_font_map_[dispatcher], font_name)) { // Requested font is new to cache. @@ -69,7 +69,7 @@ } void ReleaseCachedFonts(FontCacheDispatcher* dispatcher) { - typedef std::map<base::string16, FontCache::CacheElement> FontNameToElement; + typedef std::map<std::wstring, FontCache::CacheElement> FontNameToElement; base::AutoLock lock(mutex_); @@ -126,7 +126,7 @@ FontCache() { } - std::map<base::string16, CacheElement> cache_ GUARDED_BY(mutex_); + std::map<std::wstring, CacheElement> cache_ GUARDED_BY(mutex_); DispatcherToFontNames dispatcher_font_map_ GUARDED_BY(mutex_); base::Lock mutex_;
diff --git a/content/common/pepper_plugin_list.cc b/content/common/pepper_plugin_list.cc index 78f7c5a..1ff53a0 100644 --- a/content/common/pepper_plugin_list.cc +++ b/content/common/pepper_plugin_list.cc
@@ -81,14 +81,7 @@ PepperPluginInfo plugin; plugin.is_out_of_process = out_of_process; -#if defined(OS_WIN) - // This means we can't provide plugins from non-ASCII paths, but - // since this switch is only for development I don't think that's - // too awful. - plugin.path = base::FilePath(base::ASCIIToUTF16(name_parts[0])); -#else - plugin.path = base::FilePath(name_parts[0]); -#endif + plugin.path = base::FilePath::FromUTF8Unsafe(name_parts[0]); uint64_t index_mask = 1ULL << i; if (!(skip_file_check_flags & index_mask)) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java b/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java index ca20ffa..3967e71 100644 --- a/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java +++ b/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java
@@ -14,8 +14,10 @@ import org.chromium.blink.mojom.AuthenticatorStatus; import org.chromium.content_public.browser.FeaturePolicyFeature; import org.chromium.content_public.browser.RenderFrameHost; +import org.chromium.mojo.bindings.Interface; +import org.chromium.mojo.bindings.InterfaceRequest; +import org.chromium.mojo.system.Pair; import org.chromium.mojo.system.impl.CoreImpl; -import org.chromium.services.service_manager.InterfaceProvider; import org.chromium.url.Origin; /** @@ -28,27 +30,20 @@ // mDelegate can be null. private final RenderFrameHostDelegate mDelegate; private final boolean mIncognito; - private final InterfaceProvider mInterfaceProvider; private RenderFrameHostImpl(long nativeRenderFrameHostAndroid, RenderFrameHostDelegate delegate, - boolean isIncognito, int nativeInterfaceProviderHandle) { + boolean isIncognito) { mNativeRenderFrameHostAndroid = nativeRenderFrameHostAndroid; mDelegate = delegate; mIncognito = isIncognito; - mInterfaceProvider = - new InterfaceProvider(CoreImpl.getInstance() - .acquireNativeHandle(nativeInterfaceProviderHandle) - .toMessagePipeHandle()); mDelegate.renderFrameCreated(this); } @CalledByNative private static RenderFrameHostImpl create(long nativeRenderFrameHostAndroid, - RenderFrameHostDelegate delegate, boolean isIncognito, - int nativeInterfaceProviderHandle) { - return new RenderFrameHostImpl( - nativeRenderFrameHostAndroid, delegate, isIncognito, nativeInterfaceProviderHandle); + RenderFrameHostDelegate delegate, boolean isIncognito) { + return new RenderFrameHostImpl(nativeRenderFrameHostAndroid, delegate, isIncognito); } @CalledByNative @@ -108,11 +103,6 @@ mNativeRenderFrameHostAndroid, RenderFrameHostImpl.this, feature); } - @Override - public InterfaceProvider getRemoteInterfaces() { - return mInterfaceProvider; - } - /** * TODO(timloh): This function shouldn't really be on here. If we end up * needing more logic from the native BrowserContext, we should add a @@ -125,16 +115,36 @@ @Override public void notifyUserActivation() { + if (mNativeRenderFrameHostAndroid == 0) return; RenderFrameHostImplJni.get().notifyUserActivation( mNativeRenderFrameHostAndroid, RenderFrameHostImpl.this); } @Override public boolean isRenderFrameCreated() { + if (mNativeRenderFrameHostAndroid == 0) return false; return RenderFrameHostImplJni.get().isRenderFrameCreated( mNativeRenderFrameHostAndroid, RenderFrameHostImpl.this); } + @Override + public <I extends Interface, P extends Interface.Proxy> P getInterfaceToRendererFrame( + Interface.Manager<I, P> manager) { + if (mNativeRenderFrameHostAndroid == 0) return null; + Pair<P, InterfaceRequest<I>> result = manager.getInterfaceRequest(CoreImpl.getInstance()); + RenderFrameHostImplJni.get().getInterfaceToRendererFrame(mNativeRenderFrameHostAndroid, + RenderFrameHostImpl.this, manager.getName(), + result.second.passHandle().releaseNativeHandle()); + return result.first; + } + + @Override + public void terminateRendererDueToBadMessage(int reason) { + if (mNativeRenderFrameHostAndroid == 0) return; + RenderFrameHostImplJni.get().terminateRendererDueToBadMessage( + mNativeRenderFrameHostAndroid, RenderFrameHostImpl.this, reason); + } + /** * Return the AndroidOverlay routing token for this RenderFrameHostImpl. */ @@ -183,6 +193,10 @@ long nativeRenderFrameHostAndroid, RenderFrameHostImpl caller); void notifyUserActivation(long nativeRenderFrameHostAndroid, RenderFrameHostImpl caller); boolean isRenderFrameCreated(long nativeRenderFrameHostAndroid, RenderFrameHostImpl caller); + void getInterfaceToRendererFrame(long nativeRenderFrameHostAndroid, + RenderFrameHostImpl caller, String interfacename, int messagePipeRawHandle); + void terminateRendererDueToBadMessage( + long nativeRenderFrameHostAndroid, RenderFrameHostImpl caller, int reason); boolean isProcessBlocked(long nativeRenderFrameHostAndroid, RenderFrameHostImpl caller); int performGetAssertionWebAuthSecurityChecks(long nativeRenderFrameHostAndroid, RenderFrameHostImpl caller, String relyingPartyId, Origin effectiveOrigin);
diff --git a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectInjector.java b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectInjector.java index 0750ca5..1bff2f8 100644 --- a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectInjector.java +++ b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectInjector.java
@@ -104,7 +104,11 @@ List<RenderFrameHost> frames = webContents.getAllRenderFrameHosts(); for (RenderFrameHost frame : frames) { - addInterfaceForFrame(frame, name, object, requiredAnnotation); + // If there's no renderer frame yet, we will add the interface when + // it is created. + if (frame.isRenderFrameCreated()) { + addInterfaceForFrame(frame, name, object, requiredAnnotation); + } } } @@ -166,8 +170,8 @@ RemoteObjectHostImpl host = new RemoteObjectHostImpl( new RemoteObjectAuditorImpl(), registry, mAllowInspection); - RemoteObjectGatewayFactory factory = frameHost.getRemoteInterfaces().getInterface( - RemoteObjectGatewayFactory.MANAGER); + RemoteObjectGatewayFactory factory = + frameHost.getInterfaceToRendererFrame(RemoteObjectGatewayFactory.MANAGER); Pair<RemoteObjectGateway.Proxy, InterfaceRequest<RemoteObjectGateway>> result = RemoteObjectGateway.MANAGER.getInterfaceRequest(CoreImpl.getInstance());
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/RenderFrameHost.java b/content/public/android/java/src/org/chromium/content_public/browser/RenderFrameHost.java index cd63bb5..bff32790 100644 --- a/content/public/android/java/src/org/chromium/content_public/browser/RenderFrameHost.java +++ b/content/public/android/java/src/org/chromium/content_public/browser/RenderFrameHost.java
@@ -7,7 +7,7 @@ import androidx.annotation.Nullable; import org.chromium.base.Callback; -import org.chromium.services.service_manager.InterfaceProvider; +import org.chromium.mojo.bindings.Interface; import org.chromium.url.Origin; /** @@ -49,13 +49,28 @@ boolean isFeatureEnabled(@FeaturePolicyFeature int feature); /** - * Returns an InterfaceProvider that provides access to interface implementations provided by - * the corresponding RenderFrame. This provides access to interfaces implemented in the renderer - * to Java code in the browser process. + * Returns an interface by name to the Frame in the renderer process. This + * provides access to interfaces implemented in the renderer to Java code in + * the browser process. * - * @return The InterfaceProvider for the frame. + * Callers are responsible to ensure that the renderer Frame exists before + * trying to make a mojo connection to it. This can be done via + * isRenderFrameCreated() if the caller is not inside the call-stack of an + * IPC form the renderer (which would guarantee its existence at that time). + * + * @param pipe The message pipe to be connected to the renderer. If it fails + * to make the connection, the pipe will be closed. */ - InterfaceProvider getRemoteInterfaces(); + <I extends Interface, P extends Interface.Proxy> P getInterfaceToRendererFrame( + Interface.Manager<I, P> manager); + + /** + * Kills the renderer process when it is detected to be misbehaving and has + * made a bad request. + * + * @param reason The BadMessageReason code from content::BadMessageReasons. + */ + void terminateRendererDueToBadMessage(int reason); /** * Notifies the native RenderFrameHost about a user activation from the browser side.
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc index adc83e8..7aaf30b 100644 --- a/content/public/browser/content_browser_client.cc +++ b/content/public/browser/content_browser_client.cc
@@ -714,12 +714,12 @@ return true; } -base::string16 ContentBrowserClient::GetAppContainerSidForSandboxType( +std::wstring ContentBrowserClient::GetAppContainerSidForSandboxType( sandbox::policy::SandboxType sandbox_type) { // Embedders should override this method and return different SIDs for each // sandbox type. Note: All content level tests will run child processes in the // same AppContainer. - return base::string16( + return std::wstring( L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-" L"924012148-129201922"); }
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h index b5796830..a2ae9cb 100644 --- a/content/public/browser/content_browser_client.h +++ b/content/public/browser/content_browser_client.h
@@ -1211,7 +1211,7 @@ // Returns the AppContainer SID for the specified sandboxed process type, or // empty string if this sandboxed process type does not support living inside // an AppContainer. Called on PROCESS_LAUNCHER thread. - virtual base::string16 GetAppContainerSidForSandboxType( + virtual std::wstring GetAppContainerSidForSandboxType( sandbox::policy::SandboxType sandbox_type); // Returns whether renderer code integrity is enabled.
diff --git a/content/public/browser/media_stream_request.h b/content/public/browser/media_stream_request.h index a85b2b2f..8845ecf 100644 --- a/content/public/browser/media_stream_request.h +++ b/content/public/browser/media_stream_request.h
@@ -114,9 +114,6 @@ virtual void OnDeviceStopped(const std::string& label, const DesktopMediaID& media_id) = 0; - - // Replaces the stop callback set in OnStarted(), if any. - virtual void SetStopCallback(base::OnceClosure stop) = 0; }; // Callback used return results of media access requests.
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc index 0947f18a..42de19e2 100644 --- a/content/public/common/content_features.cc +++ b/content/public/common/content_features.cc
@@ -68,8 +68,17 @@ base::FEATURE_ENABLED_BY_DEFAULT}; // Enable using the BackForwardCache. +// BackForwardCache is enabled only on Android for the moment, as some +// desktop-specific features (including extensions) are not compatible with +// bfcache yet. Tracking bug for enabling bfcache on desktop: +// https://crbug.com/1171298. +#if defined(OS_ANDROID) +const base::Feature kBackForwardCache{"BackForwardCache", + base::FEATURE_ENABLED_BY_DEFAULT}; +#else const base::Feature kBackForwardCache{"BackForwardCache", base::FEATURE_DISABLED_BY_DEFAULT}; +#endif // BackForwardCache is disabled on low memory devices. The threshold is defined // via a field trial param: "memory_threshold_for_back_forward_cache_in_mb" @@ -78,8 +87,16 @@ // "BackForwardCacheMemoryControls" is checked before "BackForwardCache". It // means the low memory devices will activate neither the control group nor the // experimental group of the BackForwardCache field trial. + +// BackForwardCacheMemoryControls is enabled only on Android to disable +// BackForwardCache for lower memory devices due to memory limiations. +#if defined(OS_ANDROID) +const base::Feature kBackForwardCacheMemoryControl{ + "BackForwardCacheMemoryControls", base::FEATURE_ENABLED_BY_DEFAULT}; +#else const base::Feature kBackForwardCacheMemoryControl{ "BackForwardCacheMemoryControls", base::FEATURE_DISABLED_BY_DEFAULT}; +#endif // Block subresource requests whose URLs contain embedded credentials (e.g. // `https://user:pass@example.com/resource`).
diff --git a/content/public/test/android/BUILD.gn b/content/public/test/android/BUILD.gn index 6449c38..d905a475 100644 --- a/content/public/test/android/BUILD.gn +++ b/content/public/test/android/BUILD.gn
@@ -22,6 +22,7 @@ "//base:base_java", "//base:base_java_test_support", "//content/public/android:content_java", + "//mojo/public/java:bindings_java", "//net/android:net_java", "//services/service_manager/public/java:service_manager_java", "//third_party/android_deps:androidx_annotation_annotation_java",
diff --git a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/mock/MockRenderFrameHost.java b/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/mock/MockRenderFrameHost.java index 6524bc6..eb77bb7 100644 --- a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/mock/MockRenderFrameHost.java +++ b/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/mock/MockRenderFrameHost.java
@@ -7,7 +7,7 @@ import org.chromium.base.Callback; import org.chromium.content_public.browser.FeaturePolicyFeature; import org.chromium.content_public.browser.RenderFrameHost; -import org.chromium.services.service_manager.InterfaceProvider; +import org.chromium.mojo.bindings.Interface; import org.chromium.url.Origin; /** @@ -33,11 +33,15 @@ } @Override - public InterfaceProvider getRemoteInterfaces() { + public <I extends Interface, P extends Interface.Proxy> P getInterfaceToRendererFrame( + Interface.Manager<I, P> manager) { return null; } @Override + public void terminateRendererDueToBadMessage(int reason) {} + + @Override public void notifyUserActivation() {} @Override
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc index 148802b..e30f107 100644 --- a/content/public/test/browser_test_utils.cc +++ b/content/public/test/browser_test_utils.cc
@@ -2190,8 +2190,8 @@ ASSERT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPropertyValue( UIA_NamePropertyId, name.Receive())); ASSERT_EQ(VT_BSTR, name.type()); - names.push_back(base::UTF16ToUTF8( - base::string16(V_BSTR(name.ptr()), SysStringLen(V_BSTR(name.ptr()))))); + names.push_back(base::WideToUTF8( + std::wstring(V_BSTR(name.ptr()), SysStringLen(V_BSTR(name.ptr()))))); } ASSERT_THAT(names, testing::UnorderedElementsAreArray(expected_names));
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc index 8049d80..c7d9dbce 100644 --- a/content/renderer/accessibility/blink_ax_tree_source.cc +++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -80,22 +80,6 @@ dst->AddIntListAttribute(attr, ids); } -WebAXObject ParentObjectUnignored(WebAXObject child) { - WebAXObject parent = child.ParentObject(); - while (!parent.IsDetached() && !parent.AccessibilityIsIncludedInTree()) - parent = parent.ParentObject(); - return parent; -} - -// Returns true if |ancestor| is the first unignored parent of |child|, -// which means that when walking up the parent chain from |child|, -// |ancestor| is the *first* ancestor that isn't marked as -// accessibilityIsIgnored(). -bool IsParentUnignoredOf(WebAXObject ancestor, WebAXObject child) { - WebAXObject parent = ParentObjectUnignored(child); - return parent.Equals(ancestor); -} - // Helper function that searches in the subtree of |obj| to a max // depth of |max_depth| for an image. // @@ -422,19 +406,15 @@ WebAXObject child = parent.ChildAt(i); // The child may be invalid due to issues in blink accessibility code. - if (child.IsDetached()) + if (child.IsDetached()) { + NOTREACHED(); continue; + } - // Skip children whose parent isn't |parent|. - // As an exception, include children of an iframe element. - if (!is_iframe && !IsParentUnignoredOf(parent, child)) - continue; - - // Skip table headers and columns, they're only needed on Mac - // and soon we'll get rid of this code entirely. - if (child.Role() == ax::mojom::Role::kColumn || - child.Role() == ax::mojom::Role::kTableHeaderContainer) - continue; + // These should not be produced by Blink. They are only needed on Mac and + // handled in AXTableInfo on the browser side. + DCHECK_NE(child.Role(), ax::mojom::Role::kColumn); + DCHECK_NE(child.Role(), ax::mojom::Role::kTableHeaderContainer); // If an optional exclude_offscreen flag is set (only intended to be // used for a one-time snapshot of the accessibility tree), prune any @@ -539,23 +519,6 @@ dst->AddBoolAttribute(ax::mojom::BoolAttribute::kHasAriaAttribute, true); } - // Add the ids of *indirect* children - those who are children of this node, - // but whose parent is *not* this node. One example is a table - // cell, which is a child of both a row and a column. Because the cell's - // parent is the row, the row adds it as a child, and the column adds it - // as an indirect child. - int child_count = src.ChildCount(); - std::vector<int32_t> indirect_child_ids; - for (int i = 0; i < child_count; ++i) { - WebAXObject child = src.ChildAt(i); - if (!is_iframe && !child.IsDetached() && !IsParentUnignoredOf(src, child)) - indirect_child_ids.push_back(child.AxID()); - } - if (indirect_child_ids.size() > 0) { - dst->AddIntListAttribute(ax::mojom::IntListAttribute::kIndirectChildIds, - indirect_child_ids); - } - if (dst->id == image_data_node_id_) { // In general, string attributes should be truncated using // TruncateAndAddStringAttribute, but ImageDataUrl contains a data url
diff --git a/content/renderer/media/inspector_media_event_handler.cc b/content/renderer/media/inspector_media_event_handler.cc index 1d3a593..812a929 100644 --- a/content/renderer/media/inspector_media_event_handler.cc +++ b/content/renderer/media/inspector_media_event_handler.cc
@@ -108,6 +108,7 @@ void InspectorMediaEventHandler::OnWebMediaPlayerDestroyed() { video_player_destroyed_ = true; + inspector_context_->DestroyPlayer(player_id_); } } // namespace content
diff --git a/content/renderer/media/inspector_media_event_handler_unittest.cc b/content/renderer/media/inspector_media_event_handler_unittest.cc index f647f46..85926fb 100644 --- a/content/renderer/media/inspector_media_event_handler_unittest.cc +++ b/content/renderer/media/inspector_media_event_handler_unittest.cc
@@ -22,6 +22,10 @@ blink::WebString CreatePlayer() override { return "TestPlayer"; } + void DestroyPlayer(const blink::WebString& playerId) override { + MockDestroyPlayer(); + } + void NotifyPlayerEvents(blink::WebString id, const blink::InspectorPlayerEvents& events) override { MockNotifyPlayerEvents(events); @@ -48,6 +52,9 @@ MOCK_METHOD1(MockSetPlayerProperties, void(blink::InspectorPlayerProperties)); MOCK_METHOD1(MockNotifyPlayerErrors, void(blink::InspectorPlayerErrors)); MOCK_METHOD1(MockNotifyPlayerMessages, void(blink::InspectorPlayerMessages)); + MOCK_METHOD0(MockDestroyPlayer, void()); + MOCK_METHOD0(MockIncrementActiveSessionCount, void()); + MOCK_METHOD0(MockDecrementActiveSessionCount, void()); }; class InspectorMediaEventHandlerTest : public testing::Test {
diff --git a/content/renderer/media/media_interface_factory.cc b/content/renderer/media/media_interface_factory.cc index 24a0f6f..6991ed9 100644 --- a/content/renderer/media/media_interface_factory.cc +++ b/content/renderer/media/media_interface_factory.cc
@@ -138,6 +138,26 @@ } #endif // defined(OS_ANDROID) +#if defined(OS_WIN) +void MediaInterfaceFactory::CreateMediaFoundationRenderer( + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) { + if (!task_runner_->BelongsToCurrentThread()) { + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&MediaInterfaceFactory::CreateMediaFoundationRenderer, + weak_this_, std::move(receiver), + std::move(renderer_extension_receiver))); + return; + } + + DVLOG(1) << __func__; + GetMediaInterfaceFactory()->CreateMediaFoundationRenderer( + std::move(receiver), std::move(renderer_extension_receiver)); +} +#endif // defined(OS_WIN) + void MediaInterfaceFactory::CreateCdm(const std::string& key_system, const media::CdmConfig& cdm_config, CreateCdmCallback callback) {
diff --git a/content/renderer/media/media_interface_factory.h b/content/renderer/media/media_interface_factory.h index f9e8707..53b425d 100644 --- a/content/renderer/media/media_interface_factory.h +++ b/content/renderer/media/media_interface_factory.h
@@ -69,6 +69,12 @@ mojo::PendingReceiver<media::mojom::MediaPlayerRendererExtension> renderer_extension_receiver) final; #endif // defined(OS_ANDROID) +#if defined(OS_WIN) + void CreateMediaFoundationRenderer( + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) final; +#endif // defined(OS_WIN) void CreateCdm(const std::string& key_system, const media::CdmConfig& cdm_config, CreateCdmCallback callback) final;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index a52fcdf..f6a784cf 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc
@@ -923,10 +923,10 @@ } #if defined(OS_WIN) - base::string16 path = request->Url().GetString().Utf16(); - const base::string16 file_prefix = - base::ASCIIToUTF16(url::kFileScheme) + - base::ASCIIToUTF16(url::kStandardSchemeSeparator); + std::wstring path = base::UTF16ToWide(request->Url().GetString().Utf16()); + const std::wstring file_prefix = + base::ASCIIToWide(url::kFileScheme) + + base::ASCIIToWide(url::kStandardSchemeSeparator); #else std::string path = request->Url().GetString().Utf8(); const std::string file_prefix =
diff --git a/content/test/dwrite_font_fake_sender_win.cc b/content/test/dwrite_font_fake_sender_win.cc index 2145e13..e916c00 100644 --- a/content/test/dwrite_font_fake_sender_win.cc +++ b/content/test/dwrite_font_fake_sender_win.cc
@@ -10,12 +10,13 @@ #include <memory> #include "base/bind.h" +#include "base/strings/string_util.h" namespace content { void AddFamily(const base::FilePath& font_path, - const base::string16& family_name, - const base::string16& base_family_name, + const std::wstring& family_name, + const std::wstring& base_family_name, FakeFontCollection* collection) { collection->AddFont(family_name) .AddFamilyName(L"en-us", family_name) @@ -80,7 +81,8 @@ FindFamilyCallback callback) { message_types_.push_back(MessageType::kFindFamily); for (size_t n = 0; n < fonts_.size(); n++) { - if (_wcsicmp(family_name.data(), fonts_[n].font_name_.data()) == 0) { + if (base::EqualsCaseInsensitiveASCII(family_name.data(), + fonts_[n].font_name_.data())) { std::move(callback).Run(n); return; }
diff --git a/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.cc b/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.cc index 8877668..9594101 100644 --- a/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.cc +++ b/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.cc
@@ -209,7 +209,7 @@ base::MakeRefCounted<content::CacheStorageContextImpl>(); cache_storage_context_->Init(browser_context_->GetPath(), browser_context_->GetSpecialStoragePolicy(), - nullptr); + nullptr, mojo::NullRemote()); cache_storage_context_->Bind( cache_storage_control_.BindNewPipeAndPassReceiver());
diff --git a/content/test/gpu/gpu_tests/common_browser_args.py b/content/test/gpu/gpu_tests/common_browser_args.py index e2a7426..9230fe49 100644 --- a/content/test/gpu/gpu_tests/common_browser_args.py +++ b/content/test/gpu/gpu_tests/common_browser_args.py
@@ -28,8 +28,9 @@ ENABLE_EXPERIMENTAL_WEB_PLATFORM_FEATURES =\ '--enable-experimental-web-platform-features' ENABLE_GPU_BENCHMARKING = '--enable-gpu-benchmarking' +ENABLE_GPU_RASTERIZATION = '--enable-gpu-rasterization' ENABLE_LOGGING = '--enable-logging' ENSURE_FORCED_COLOR_PROFILE = '--ensure-forced-color-profile' +FORCE_BROWSER_CRASH_ON_GPU_CRASH = '--force-browser-crash-on-gpu-crash' FORCE_COLOR_PROFILE_SRGB = '--force-color-profile=srgb' -ENABLE_GPU_RASTERIZATION = '--enable-gpu-rasterization' TEST_TYPE_GPU = '--test-type=gpu'
diff --git a/content/test/gpu/gpu_tests/maps_integration_test.py b/content/test/gpu/gpu_tests/maps_integration_test.py index 4d7e013..a6bdcd89 100644 --- a/content/test/gpu/gpu_tests/maps_integration_test.py +++ b/content/test/gpu/gpu_tests/maps_integration_test.py
@@ -47,8 +47,9 @@ options.dont_restore_color_profile_after_test) super(MapsIntegrationTest, cls).SetUpProcess() cls.CustomizeBrowserArgs([ - cba.FORCE_COLOR_PROFILE_SRGB, cba.ENSURE_FORCED_COLOR_PROFILE, + cba.FORCE_BROWSER_CRASH_ON_GPU_CRASH, + cba.FORCE_COLOR_PROFILE_SRGB, ]) cloud_storage.GetIfChanged( os.path.join(_MAPS_PERF_TEST_PATH, 'load_dataset'),
diff --git a/device/vr/android/arcore/arcore.cc b/device/vr/android/arcore/arcore.cc index a7db8b7..8fe97945 100644 --- a/device/vr/android/arcore/arcore.cc +++ b/device/vr/android/arcore/arcore.cc
@@ -26,11 +26,29 @@ } ArCore::InitializeResult::InitializeResult( - const std::unordered_set<device::mojom::XRSessionFeature>& enabled_features) - : enabled_features(enabled_features) {} + const std::unordered_set<device::mojom::XRSessionFeature>& enabled_features, + base::Optional<device::mojom::XRDepthConfig> depth_configuration) + : enabled_features(enabled_features), + depth_configuration(std::move(depth_configuration)) {} ArCore::InitializeResult::InitializeResult(const InitializeResult& other) = default; ArCore::InitializeResult::~InitializeResult() = default; +ArCore::DepthSensingConfiguration::DepthSensingConfiguration( + std::vector<device::mojom::XRDepthUsage> depth_usage_preference, + std::vector<device::mojom::XRDepthDataFormat> depth_data_format_preference) + : depth_usage_preference(depth_usage_preference), + depth_data_format_preference(depth_data_format_preference) {} + +ArCore::DepthSensingConfiguration::DepthSensingConfiguration( + DepthSensingConfiguration&& other) = default; +ArCore::DepthSensingConfiguration::DepthSensingConfiguration( + const DepthSensingConfiguration& other) = default; +ArCore::DepthSensingConfiguration::~DepthSensingConfiguration() = default; +ArCore::DepthSensingConfiguration& ArCore::DepthSensingConfiguration::operator=( + const DepthSensingConfiguration& other) = default; +ArCore::DepthSensingConfiguration& ArCore::DepthSensingConfiguration::operator=( + DepthSensingConfiguration&& other) = default; + } // namespace device
diff --git a/device/vr/android/arcore/arcore.h b/device/vr/android/arcore/arcore.h index 066a51d4..61b08b8 100644 --- a/device/vr/android/arcore/arcore.h +++ b/device/vr/android/arcore/arcore.h
@@ -37,12 +37,37 @@ struct InitializeResult { std::unordered_set<device::mojom::XRSessionFeature> enabled_features; - InitializeResult(const std::unordered_set<device::mojom::XRSessionFeature>& - enabled_features); + // If the depth sensing API was requested, the depth_configuration will + // contain the device-selected depth API usage and data format. + + base::Optional<device::mojom::XRDepthConfig> depth_configuration; + + InitializeResult( + const std::unordered_set<device::mojom::XRSessionFeature>& + enabled_features, + base::Optional<device::mojom::XRDepthConfig> depth_configuration); InitializeResult(const InitializeResult& other); ~InitializeResult(); }; + struct DepthSensingConfiguration { + std::vector<device::mojom::XRDepthUsage> depth_usage_preference; + std::vector<device::mojom::XRDepthDataFormat> depth_data_format_preference; + + DepthSensingConfiguration( + std::vector<device::mojom::XRDepthUsage> depth_usage_preference, + std::vector<device::mojom::XRDepthDataFormat> + depth_data_format_preference); + ~DepthSensingConfiguration(); + + DepthSensingConfiguration(const DepthSensingConfiguration& other); + DepthSensingConfiguration(DepthSensingConfiguration&& other); + + DepthSensingConfiguration& operator=( + const DepthSensingConfiguration& other); + DepthSensingConfiguration& operator=(DepthSensingConfiguration&& other); + }; + // Initializes the runtime and returns whether it was successful. // If successful, the runtime must be paused when this method returns. virtual base::Optional<InitializeResult> Initialize( @@ -51,7 +76,8 @@ required_features, const std::unordered_set<device::mojom::XRSessionFeature>& optional_features, - const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images) = 0; + const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images, + base::Optional<DepthSensingConfiguration> depth_sensing_config) = 0; // Returns the target framerate range in Hz. Actual capture frame rate will // vary within this range, i.e. lower in low light to increase exposure time.
diff --git a/device/vr/android/arcore/arcore_device.cc b/device/vr/android/arcore/arcore_device.cc index 4189728..780abbb 100644 --- a/device/vr/android/arcore/arcore_device.cc +++ b/device/vr/android/arcore/arcore_device.cc
@@ -124,12 +124,14 @@ session_state_->optional_features_.insert(options->optional_features.begin(), options->optional_features.end()); - bool use_dom_overlay = + const bool use_dom_overlay = base::Contains(options->required_features, device::mojom::XRSessionFeature::DOM_OVERLAY) || base::Contains(options->optional_features, device::mojom::XRSessionFeature::DOM_OVERLAY); + session_state_->depth_options_ = std::move(options->depth_options); + // mailbox_bridge_ is either supplied from the constructor, or recreated in // OnSessionEnded(). DCHECK(mailbox_bridge_); @@ -247,9 +249,8 @@ } void ArCoreDevice::CallDeferredRequestSessionCallback( - base::Optional<std::unordered_set<device::mojom::XRSessionFeature>> - enabled_features) { - DVLOG(1) << __func__ << " success=" << enabled_features.has_value(); + base::Optional<ArCoreGlInitializeResult> initialize_result) { + DVLOG(1) << __func__ << " success=" << initialize_result.has_value(); DCHECK(IsOnMainThread()); // We might not have any pending session requests, i.e. if destroyed @@ -260,15 +261,15 @@ mojom::XRRuntime::RequestSessionCallback deferred_callback = std::move(session_state_->pending_request_session_callback_); - if (!enabled_features) { + if (!initialize_result) { std::move(deferred_callback).Run(nullptr, mojo::NullRemote()); return; } // Success case should only happen after GL thread is ready. - auto create_callback = - base::BindOnce(&ArCoreDevice::OnCreateSessionCallback, GetWeakPtr(), - std::move(deferred_callback), *enabled_features); + auto create_callback = base::BindOnce( + &ArCoreDevice::OnCreateSessionCallback, GetWeakPtr(), + std::move(deferred_callback), std::move(*initialize_result)); auto shutdown_callback = base::BindOnce(&ArCoreDevice::OnSessionEnded, GetWeakPtr()); @@ -283,24 +284,26 @@ void ArCoreDevice::OnCreateSessionCallback( mojom::XRRuntime::RequestSessionCallback deferred_callback, - const std::unordered_set<device::mojom::XRSessionFeature>& enabled_features, - mojo::PendingRemote<mojom::XRFrameDataProvider> frame_data_provider, - mojom::VRDisplayInfoPtr display_info, - mojo::PendingRemote<mojom::XRSessionController> session_controller, - mojom::XRPresentationConnectionPtr presentation_connection) { + ArCoreGlInitializeResult initialize_result, + ArCoreGlCreateSessionResult create_session_result) { DVLOG(2) << __func__; DCHECK(IsOnMainThread()); mojom::XRSessionPtr session = mojom::XRSession::New(); - session->data_provider = std::move(frame_data_provider); - session->display_info = std::move(display_info); - session->submit_frame_sink = std::move(presentation_connection); - session->enabled_features.assign(enabled_features.begin(), - enabled_features.end()); + session->data_provider = std::move(create_session_result.frame_data_provider); + session->display_info = std::move(create_session_result.display_info); + session->submit_frame_sink = + std::move(create_session_result.presentation_connection); + session->enabled_features.assign(initialize_result.enabled_features.begin(), + initialize_result.enabled_features.end()); session->device_config = device::mojom::XRSessionDeviceConfig::New(); auto* config = session->device_config.get(); config->supports_viewport_scaling = true; + config->depth_configuration = + initialize_result.depth_configuration + ? initialize_result.depth_configuration->Clone() + : nullptr; // ARCORE only supports immersive-ar sessions session->enviroment_blend_mode = @@ -308,7 +311,8 @@ session->interaction_mode = device::mojom::XRInteractionMode::kScreenSpace; std::move(deferred_callback) - .Run(std::move(session), std::move(session_controller)); + .Run(std::move(session), + std::move(create_session_result.session_controller)); } void ArCoreDevice::PostTaskToGlThread(base::OnceClosure task) { @@ -348,27 +352,39 @@ frame_size, rotation, session_state_->required_features_, session_state_->optional_features_, std::move(session_state_->tracked_images_), + std::move(session_state_->depth_options_), CreateMainThreadCallback(base::BindOnce( &ArCoreDevice::OnArCoreGlInitializationComplete, GetWeakPtr())))); return; } - OnArCoreGlInitializationComplete(session_state_->enabled_features_); + // Since the GL is already initialized, we already have session_state_ that we + // can pass along. + OnArCoreGlInitializationComplete(ArCoreGlInitializeResult( + session_state_->enabled_features_, session_state_->depth_configuration_)); } void ArCoreDevice::OnArCoreGlInitializationComplete( - base::Optional<std::unordered_set<device::mojom::XRSessionFeature>> - enabled_features) { + base::Optional<ArCoreGlInitializeResult> arcore_initialization_result) { DVLOG(1) << __func__; DCHECK(IsOnMainThread()); - session_state_->is_arcore_gl_initialized_ = enabled_features.has_value(); - session_state_->enabled_features_ = enabled_features.value_or( - std::unordered_set<device::mojom::XRSessionFeature>{}); + session_state_->is_arcore_gl_initialized_ = + arcore_initialization_result.has_value(); + + if (arcore_initialization_result) { + session_state_->enabled_features_ = + arcore_initialization_result->enabled_features; + session_state_->depth_configuration_ = + arcore_initialization_result->depth_configuration; + } else { + session_state_->enabled_features_ = {}; + session_state_->depth_configuration_ = base::nullopt; + } // We only start GL initialization after the user has granted consent, so we // can now start the session. - CallDeferredRequestSessionCallback(enabled_features); + CallDeferredRequestSessionCallback(std::move(arcore_initialization_result)); } } // namespace device
diff --git a/device/vr/android/arcore/arcore_device.h b/device/vr/android/arcore/arcore_device.h index 4df2c76..23abf167 100644 --- a/device/vr/android/arcore/arcore_device.h +++ b/device/vr/android/arcore/arcore_device.h
@@ -15,6 +15,7 @@ #include "base/callback.h" #include "base/macros.h" #include "base/optional.h" +#include "device/vr/android/arcore/arcore_gl.h" #include "device/vr/vr_device.h" #include "device/vr/vr_device_base.h" #include "mojo/public/cpp/bindings/pending_remote.h" @@ -90,8 +91,7 @@ // Replies to the pending mojo RequestSession request. void CallDeferredRequestSessionCallback( - base::Optional<std::unordered_set<device::mojom::XRSessionFeature>> - enabled_features); + base::Optional<ArCoreGlInitializeResult> arcore_initialization_result); // Tells the GL thread to initialize a GL context and other resources, // using the supplied window as a drawing surface. @@ -101,17 +101,12 @@ // Called when the GL thread's GL context initialization completes. void OnArCoreGlInitializationComplete( - base::Optional<std::unordered_set<device::mojom::XRSessionFeature>> - enabled_features); + base::Optional<ArCoreGlInitializeResult> arcore_initialization_result); void OnCreateSessionCallback( mojom::XRRuntime::RequestSessionCallback deferred_callback, - const std::unordered_set<device::mojom::XRSessionFeature>& - enabled_features, - mojo::PendingRemote<mojom::XRFrameDataProvider> frame_data_provider, - mojom::VRDisplayInfoPtr display_info, - mojo::PendingRemote<mojom::XRSessionController> session_controller, - mojom::XRPresentationConnectionPtr presentation_connection); + ArCoreGlInitializeResult initialize_result, + ArCoreGlCreateSessionResult create_session_result); scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_; std::unique_ptr<ArCoreFactory> arcore_factory_; @@ -140,10 +135,14 @@ std::unordered_set<device::mojom::XRSessionFeature> required_features_; std::unordered_set<device::mojom::XRSessionFeature> optional_features_; + device::mojom::XRDepthOptionsPtr depth_options_; + // Collection of features that have been enabled on the session. Initially // empty, will be set only once the ArCoreGl has been initialized. std::unordered_set<device::mojom::XRSessionFeature> enabled_features_; + base::Optional<device::mojom::XRDepthConfig> depth_configuration_; + std::vector<device::mojom::XRTrackedImagePtr> tracked_images_; };
diff --git a/device/vr/android/arcore/arcore_gl.cc b/device/vr/android/arcore/arcore_gl.cc index b66f3ba..bc175c7 100644 --- a/device/vr/android/arcore/arcore_gl.cc +++ b/device/vr/android/arcore/arcore_gl.cc
@@ -82,6 +82,28 @@ namespace device { +ArCoreGlCreateSessionResult::ArCoreGlCreateSessionResult( + mojo::PendingRemote<mojom::XRFrameDataProvider> frame_data_provider, + mojom::VRDisplayInfoPtr display_info, + mojo::PendingRemote<mojom::XRSessionController> session_controller, + mojom::XRPresentationConnectionPtr presentation_connection) + : frame_data_provider(std::move(frame_data_provider)), + display_info(std::move(display_info)), + session_controller(std::move(session_controller)), + presentation_connection(std::move(presentation_connection)) {} +ArCoreGlCreateSessionResult::~ArCoreGlCreateSessionResult() = default; +ArCoreGlCreateSessionResult::ArCoreGlCreateSessionResult( + ArCoreGlCreateSessionResult&& other) = default; + +ArCoreGlInitializeResult::ArCoreGlInitializeResult( + std::unordered_set<device::mojom::XRSessionFeature> enabled_features, + base::Optional<device::mojom::XRDepthConfig> depth_configuration) + : enabled_features(enabled_features), + depth_configuration(depth_configuration) {} +ArCoreGlInitializeResult::ArCoreGlInitializeResult( + ArCoreGlInitializeResult&& other) = default; +ArCoreGlInitializeResult::~ArCoreGlInitializeResult() = default; + ArCoreGl::ArCoreGl(std::unique_ptr<ArImageTransport> ar_image_transport) : gl_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()), ar_image_transport_(std::move(ar_image_transport)), @@ -117,6 +139,7 @@ const std::unordered_set<device::mojom::XRSessionFeature>& optional_features, const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images, + device::mojom::XRDepthOptionsPtr depth_options, ArCoreGlInitializeCallback callback) { DVLOG(3) << __func__; @@ -142,10 +165,18 @@ return; } + base::Optional<ArCore::DepthSensingConfiguration> depth_sensing_config; + if (depth_options) { + depth_sensing_config = ArCore::DepthSensingConfiguration( + depth_options->usage_preferences, + depth_options->data_format_preferences); + } + arcore_ = arcore_factory->Create(); base::Optional<ArCore::InitializeResult> maybe_initialize_result = arcore_->Initialize(application_context, required_features, - optional_features, tracked_images); + optional_features, tracked_images, + std::move(depth_sensing_config)); if (!maybe_initialize_result) { DLOG(ERROR) << "ARCore failed to initialize"; std::move(callback).Run(base::nullopt); @@ -156,6 +187,7 @@ // behavior of local and unbounded spaces & send appropriate data back in // GetFrameData(). enabled_features_ = maybe_initialize_result->enabled_features; + depth_configuration_ = maybe_initialize_result->depth_configuration; DVLOG(3) << "ar_image_transport_->Initialize()..."; ar_image_transport_->Initialize( @@ -174,7 +206,8 @@ DVLOG(3) << __func__; is_initialized_ = true; webxr_->NotifyMailboxBridgeReady(); - std::move(callback).Run(enabled_features_); + std::move(callback).Run( + ArCoreGlInitializeResult(enabled_features_, depth_configuration_)); } void ArCoreGl::CreateSession(mojom::VRDisplayInfoPtr display_info, @@ -217,11 +250,12 @@ display_info_ = std::move(display_info); - std::move(create_callback) - .Run(frame_data_receiver_.BindNewPipeAndPassRemote(), - display_info_->Clone(), - session_controller_receiver_.BindNewPipeAndPassRemote(), - std::move(submit_frame_sink)); + ArCoreGlCreateSessionResult result( + frame_data_receiver_.BindNewPipeAndPassRemote(), display_info_->Clone(), + session_controller_receiver_.BindNewPipeAndPassRemote(), + std::move(submit_frame_sink)); + + std::move(create_callback).Run(std::move(result)); frame_data_receiver_.set_disconnect_handler(base::BindOnce( &ArCoreGl::OnBindingDisconnect, weak_ptr_factory_.GetWeakPtr()));
diff --git a/device/vr/android/arcore/arcore_gl.h b/device/vr/android/arcore/arcore_gl.h index 9c5d704..cd69f1c1 100644 --- a/device/vr/android/arcore/arcore_gl.h +++ b/device/vr/android/arcore/arcore_gl.h
@@ -50,14 +50,37 @@ class ArImageTransport; class WebXrPresentationState; -using ArCoreGlCreateSessionCallback = base::OnceCallback<void( - mojo::PendingRemote<mojom::XRFrameDataProvider> frame_data_provider, - mojom::VRDisplayInfoPtr display_info, - mojo::PendingRemote<mojom::XRSessionController> session_controller, - mojom::XRPresentationConnectionPtr presentation_connection)>; +struct ArCoreGlCreateSessionResult { + mojo::PendingRemote<mojom::XRFrameDataProvider> frame_data_provider; + mojom::VRDisplayInfoPtr display_info; + mojo::PendingRemote<mojom::XRSessionController> session_controller; + mojom::XRPresentationConnectionPtr presentation_connection; -using ArCoreGlInitializeCallback = base::OnceCallback<void( - base::Optional<std::unordered_set<device::mojom::XRSessionFeature>>)>; + ArCoreGlCreateSessionResult( + mojo::PendingRemote<mojom::XRFrameDataProvider> frame_data_provider, + mojom::VRDisplayInfoPtr display_info, + mojo::PendingRemote<mojom::XRSessionController> session_controller, + mojom::XRPresentationConnectionPtr presentation_connection); + ArCoreGlCreateSessionResult(ArCoreGlCreateSessionResult&& other); + ~ArCoreGlCreateSessionResult(); +}; + +using ArCoreGlCreateSessionCallback = + base::OnceCallback<void(ArCoreGlCreateSessionResult)>; + +struct ArCoreGlInitializeResult { + std::unordered_set<device::mojom::XRSessionFeature> enabled_features; + base::Optional<device::mojom::XRDepthConfig> depth_configuration; + + ArCoreGlInitializeResult( + std::unordered_set<device::mojom::XRSessionFeature> enabled_features, + base::Optional<device::mojom::XRDepthConfig> depth_configuration); + ArCoreGlInitializeResult(ArCoreGlInitializeResult&& other); + ~ArCoreGlInitializeResult(); +}; + +using ArCoreGlInitializeCallback = + base::OnceCallback<void(base::Optional<ArCoreGlInitializeResult>)>; // All of this class's methods must be called on the same valid GL thread with // the exception of GetGlThreadTaskRunner() and GetWeakPtr(). @@ -80,6 +103,7 @@ const std::unordered_set<device::mojom::XRSessionFeature>& optional_features, const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images, + device::mojom::XRDepthOptionsPtr depth_options, ArCoreGlInitializeCallback callback); void CreateSession(mojom::VRDisplayInfoPtr display_info, @@ -197,6 +221,7 @@ // the session and only send out necessary data related to reference spaces to // blink. Valid after the call to |Initialize()| method. std::unordered_set<device::mojom::XRSessionFeature> enabled_features_; + base::Optional<device::mojom::XRDepthConfig> depth_configuration_; base::OnceClosure session_shutdown_callback_;
diff --git a/device/vr/android/arcore/arcore_impl.cc b/device/vr/android/arcore/arcore_impl.cc index 94cb36a6..c0cc152 100644 --- a/device/vr/android/arcore/arcore_impl.cc +++ b/device/vr/android/arcore/arcore_impl.cc
@@ -352,7 +352,8 @@ required_features, const std::unordered_set<device::mojom::XRSessionFeature>& optional_features, - const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images) { + const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images, + base::Optional<ArCore::DepthSensingConfiguration> depth_sensing_config) { DCHECK(IsOnGlThread()); DCHECK(!arcore_session_.is_valid()); @@ -383,8 +384,9 @@ DVLOG(1) << __func__ << ": ARCore incognito mode enabled"; base::Optional<std::unordered_set<device::mojom::XRSessionFeature>> - maybe_enabled_features = ConfigureFeatures( - session.get(), required_features, optional_features, tracked_images); + maybe_enabled_features = + ConfigureFeatures(session.get(), required_features, optional_features, + tracked_images, depth_sensing_config); if (!maybe_enabled_features) { DLOG(ERROR) << "Failed to configure session features"; @@ -422,7 +424,9 @@ base::PassKey<ArCoreImpl>(), arcore_session_.get()); plane_manager_ = std::make_unique<ArCorePlaneManager>( base::PassKey<ArCoreImpl>(), arcore_session_.get()); - return ArCore::InitializeResult(*maybe_enabled_features); + + return ArCore::InitializeResult(*maybe_enabled_features, + depth_configuration_); } base::Optional<std::unordered_set<device::mojom::XRSessionFeature>> @@ -432,7 +436,9 @@ required_features, const std::unordered_set<device::mojom::XRSessionFeature>& optional_features, - const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images) { + const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images, + const base::Optional<ArCore::DepthSensingConfiguration>& + depth_sensing_config) { // Let's assume we will be able to configure a session with all features - // this will be adjusted if it turns out we can only create a session w/o some // optional features. Currently, only depth sensing is not supported across @@ -505,15 +511,25 @@ device::mojom::XRSessionFeature::DEPTH) || depth_api_optional; - if (depth_api_requested) { + const bool depth_api_configuration_successful = + depth_api_requested && ConfigureDepthSensing(depth_sensing_config); + + if (depth_api_configuration_successful) { + // Don't try to set the depth mode if we know we won't be able to support + // the desired usage and data format. ArConfig_setDepthMode(ar_session, arcore_config.get(), AR_DEPTH_MODE_AUTOMATIC); } ArStatus status = ArSession_configure(ar_session, arcore_config.get()); - if (status != AR_SUCCESS && depth_api_optional) { - // Depth API is not available on some ARCore-capable devices - if it was + if (status != AR_SUCCESS && depth_api_configuration_successful && + depth_api_optional) { + // Configuring an ARCore session failed for some reason. + // Depth API may not be available on some ARCore-capable devices - if it was // requested optionally, let's try to request the session w/o it. + // Currently, Depth API is the only feature that is not supported across the + // board, so we assume that it is the reason why the session creation + // failed. DLOG(WARNING) << __func__ << ": Depth API was optionally requested and the session " @@ -535,6 +551,30 @@ return enabled_features; } +bool ArCoreImpl::ConfigureDepthSensing( + const base::Optional<ArCore::DepthSensingConfiguration>& + depth_sensing_config) { + if (!depth_sensing_config) { + return false; + } + + if (!base::Contains(depth_sensing_config->depth_usage_preference, + device::mojom::XRDepthUsage::kCPUOptimized)) { + return false; + } + + if (!base::Contains(depth_sensing_config->depth_data_format_preference, + device::mojom::XRDepthDataFormat::kLuminanceAlpha)) { + return false; + } + + depth_configuration_ = device::mojom::XRDepthConfig( + device::mojom::XRDepthUsage::kCPUOptimized, + device::mojom::XRDepthDataFormat::kLuminanceAlpha); + + return true; +} + bool ArCoreImpl::ConfigureCamera(ArSession* ar_session) { internal::ScopedArCoreObject<ArCameraConfigFilter*> camera_config_filter; ArCameraConfigFilter_create( @@ -1819,6 +1859,8 @@ // Transform needed to consume the data: result->norm_texture_from_norm_view = GetCameraUvFromScreenUvTransform(); result->size = gfx::Size(width, height); + result->raw_value_to_meters = + 1.0 / 1000.0; // DepthInMillimeters * 1/1000 = DepthInMeters DVLOG(3) << __func__ << ": norm_texture_from_norm_view=\n" << result->norm_texture_from_norm_view.ToString();
diff --git a/device/vr/android/arcore/arcore_impl.h b/device/vr/android/arcore/arcore_impl.h index c6117f5..9051b15 100644 --- a/device/vr/android/arcore/arcore_impl.h +++ b/device/vr/android/arcore/arcore_impl.h
@@ -112,7 +112,8 @@ required_features, const std::unordered_set<device::mojom::XRSessionFeature>& optional_features, - const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images) + const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images, + base::Optional<ArCore::DepthSensingConfiguration> depth_sensing_config) override; MinMaxRange GetTargetFramerateRange() override; void SetDisplayGeometry(const gfx::Size& frame_size, @@ -222,6 +223,8 @@ // list of images. The index values are needed for blink communication. std::unordered_map<int32_t, uint64_t> tracked_image_arcore_id_to_index_; + base::Optional<device::mojom::XRDepthConfig> depth_configuration_; + uint64_t next_id_ = 1; std::map<HitTestSubscriptionId, HitTestSubscriptionData> @@ -328,7 +331,17 @@ required_features, const std::unordered_set<device::mojom::XRSessionFeature>& optional_features, - const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images); + const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images, + const base::Optional<ArCore::DepthSensingConfiguration>& + depth_sensing_config); + + // Configures depth sensing API - selects depth sensing usage and mode that is + // compatible with the device. Returns false if it was unable to pick a + // supported combination of mode and data format. Affects + // |depth_sensing_usage_| and |depth_sensing_data_format_| members. + bool ConfigureDepthSensing( + const base::Optional<ArCore::DepthSensingConfiguration>& + depth_sensing_config); // Must be last. base::WeakPtrFactory<ArCoreImpl> weak_ptr_factory_{this};
diff --git a/device/vr/public/mojom/isolated_xr_service.mojom b/device/vr/public/mojom/isolated_xr_service.mojom index 321ea5a..92c7c31f 100644 --- a/device/vr/public/mojom/isolated_xr_service.mojom +++ b/device/vr/public/mojom/isolated_xr_service.mojom
@@ -55,6 +55,8 @@ int32 render_frame_id; array<XRTrackedImage> tracked_images; + + XRDepthOptions? depth_options; }; // An XRRuntime may live in the browser process or a utility process. The
diff --git a/device/vr/public/mojom/vr_service.mojom b/device/vr/public/mojom/vr_service.mojom index fd77143d..f83cc24 100644 --- a/device/vr/public/mojom/vr_service.mojom +++ b/device/vr/public/mojom/vr_service.mojom
@@ -101,6 +101,16 @@ kAdditive = 3, }; +enum XRDepthUsage { + kCPUOptimized = 1, + kGPUOptimized = 2, +}; + +enum XRDepthDataFormat { + kLuminanceAlpha = 1, + kFloat32 = 2, +}; + // These enum names come from the WebXR spec: // https://immersive-web.github.io/webxr-ar-module/#enumdef-xrinteractionmode // The interactionMode attribute describes the best space @@ -117,6 +127,25 @@ float width_in_meters; }; +// Structure used to configure depth sensing API at session creation. +// If the device is unable to create the session given the preferences expressed +// in the options, the session will not have depth sensing API enabled. In case +// it was a required feature, this means that session creation will fail. +struct XRDepthOptions { + // Ordered array of depth sensing usage preferences. At session creation, + // the device will pick the depth sensing usage value that it supports, + /// starting from the lowest-indexed one. `kNone` entry is not allowed to + // appear in this array, and the array should not be empty. + array<XRDepthUsage> usage_preferences; + // Ordered array of depth sensing data format preferences. At session + // creation, the device will pick the depth sensing data format value that it + // supports, starting from the lowest-indexed one. `kNone` entry is not + // allowed to appear in this array, and the array should not be empty. + // The data format selection happens after the device has selected depth + // sensing usage. + array<XRDepthDataFormat> data_format_preferences; +}; + struct XRSessionOptions { // Represents the type of session that is being requested. XRSessionMode mode; @@ -132,6 +161,15 @@ array<XRSessionFeature> optional_features; array<XRTrackedImage> tracked_images; + + // Must be present if either optional or required features contain + // depth sensing API. + XRDepthOptions? depth_options; +}; + +struct XRDepthConfig { + XRDepthUsage depth_usage; + XRDepthDataFormat depth_data_format; }; // This structure contains a description of the device's active configuration @@ -155,6 +193,10 @@ // Indicates whether we should enable anti-aliasing for WebGL layers. Value // comes from the underlying XR runtime. bool enable_anti_aliasing = true; + + // If the session supports depth-sensing API, the depth configuration must be + // non-null. + XRDepthConfig? depth_configuration; }; // This structure contains all the mojo interfaces for different kinds of @@ -609,6 +651,9 @@ gfx.mojom.Transform norm_texture_from_norm_view; // Size of the pixel array. Valid iff pixel_data is not null. gfx.mojom.Size size; + // Factor that needs to be applied when converting from raw values in the + // |pixel_data| buffer into meters. + float raw_value_to_meters; }; // Depth data may be the same as the one returned in the previous frame - it is
diff --git a/docs/ios/build_instructions.md b/docs/ios/build_instructions.md index 8979f13..7920f090 100644 --- a/docs/ios/build_instructions.md +++ b/docs/ios/build_instructions.md
@@ -353,6 +353,10 @@ debugger like `base::string16`, ... If you want to use `lldb` directly, name the file `~/.lldbinit` instead of `~/.lldbinit-Xcode`. +Note: if you are using `ios/build/tools/setup-gn.py` to generate the Xcode +project, the script also generate an `.lldbinit` file next to the project and +configure Xcode to use that file instead of the global one. + ### Changing the version of Xcode To change the version of Xcode used to build Chromium on iOS, please follow
diff --git a/extensions/browser/api/file_handlers/app_file_handler_util.cc b/extensions/browser/api/file_handlers/app_file_handler_util.cc index 7db779b5c..3278de1 100644 --- a/extensions/browser/api/file_handlers/app_file_handler_util.cc +++ b/extensions/browser/api/file_handlers/app_file_handler_util.cc
@@ -422,7 +422,7 @@ storage::IsolatedContext::ScopedFSHandle filesystem = isolated_context->RegisterFileSystemForPath( - storage::kFileSystemTypeNativeForPlatformApp, std::string(), path, + storage::kFileSystemTypeLocalForPlatformApp, std::string(), path, &result.registered_name); result.filesystem_id = filesystem.id(); @@ -501,7 +501,7 @@ // The file system API is only intended to operate on file entries that // correspond to a native file, selected by the user so only allow file // systems returned by the file system API or from a drag and drop operation. - if (type != storage::kFileSystemTypeNativeForPlatformApp && + if (type != storage::kFileSystemTypeLocalForPlatformApp && type != storage::kFileSystemTypeDragged) { *error = kInvalidParameters; return false;
diff --git a/extensions/browser/api/file_handlers/mime_util_unittest.cc b/extensions/browser/api/file_handlers/mime_util_unittest.cc index cbbcad1..6a9d8ba 100644 --- a/extensions/browser/api/file_handlers/mime_util_unittest.cc +++ b/extensions/browser/api/file_handlers/mime_util_unittest.cc
@@ -46,9 +46,9 @@ storage::FileSystemURL CreateNativeLocalFileSystemURL( storage::FileSystemContext* context, const base::FilePath local_path) { - return context->CreateCrackedFileSystemURL( - url::Origin::Create(GURL(kOrigin)), storage::kFileSystemTypeNativeLocal, - local_path); + return context->CreateCrackedFileSystemURL(url::Origin::Create(GURL(kOrigin)), + storage::kFileSystemTypeLocal, + local_path); } } // namespace
diff --git a/extensions/browser/api/file_system/file_system_api.cc b/extensions/browser/api/file_system/file_system_api.cc index a739091f..401c343 100644 --- a/extensions/browser/api/file_system/file_system_api.cc +++ b/extensions/browser/api/file_system/file_system_api.cc
@@ -484,8 +484,8 @@ // smoothly, all accessed paths need to be registered in the list of // external mount points. storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - name, storage::kFileSystemTypeNativeLocal, - storage::FileSystemMountOption(), path); + name, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), + path); } void FileSystemChooseEntryFunction::FilesSelected(
diff --git a/extensions/browser/api/runtime/runtime_api.cc b/extensions/browser/api/runtime/runtime_api.cc index 7e70eab..cf4f4ae0 100644 --- a/extensions/browser/api/runtime/runtime_api.cc +++ b/extensions/browser/api/runtime/runtime_api.cc
@@ -733,8 +733,7 @@ base::FilePath path = extension_->path(); storage::IsolatedContext::ScopedFSHandle filesystem = isolated_context->RegisterFileSystemForPath( - storage::kFileSystemTypeNativeLocal, std::string(), path, - &relative_path); + storage::kFileSystemTypeLocal, std::string(), path, &relative_path); content::ChildProcessSecurityPolicy* policy = content::ChildProcessSecurityPolicy::GetInstance();
diff --git a/extensions/common/api/_manifest_features.json b/extensions/common/api/_manifest_features.json index ffb970a..77faf04 100644 --- a/extensions/common/api/_manifest_features.json +++ b/extensions/common/api/_manifest_features.json
@@ -24,7 +24,6 @@ "app.content_security_policy": { "channel": "stable", "extension_types": ["platform_app"], - "min_manifest_version": 2, "allowlist": [ // Keep this list in sync with extensions_misc::kHangoutsExtensionIds but // omit the Packaged App ids. @@ -38,8 +37,7 @@ }, "app.background": { "channel": "stable", - "extension_types": ["platform_app"], - "min_manifest_version": 2 + "extension_types": ["platform_app"] }, "background": { "channel": "stable", @@ -52,13 +50,11 @@ "channel": "stable", "extension_types": [ "extension", "legacy_packaged_app", "login_screen_extension" - ], - "min_manifest_version": 2 + ] }, "background.service_worker": { "channel": "stable", - "extension_types": ["extension"], - "min_manifest_version": 2 + "extension_types": ["extension"] }, "bluetooth": [{ // Note: The "bluetooth" manifest permission is used by the @@ -122,7 +118,6 @@ "declarative_net_request": { "channel": "stable", "extension_types": ["extension"], - "min_manifest_version": 2, "feature_flag": "DeclarativeNetRequest" }, "default_locale": { @@ -305,20 +300,17 @@ }, "replacement_android_app": { "channel": "trunk", - "extension_types": ["platform_app"], - "min_manifest_version": 2 + "extension_types": ["platform_app"] }, "replacement_web_app": { "channel": "stable", - "extension_types": ["extension", "platform_app"], - "min_manifest_version": 2 + "extension_types": ["extension", "platform_app"] }, "sandbox": { "channel": "stable", "extension_types": [ "extension", "platform_app", "legacy_packaged_app" - ], - "min_manifest_version": 2 + ] }, "sockets": [ { @@ -376,7 +368,6 @@ }, "webview": { "channel": "stable", - "extension_types": ["platform_app"], - "min_manifest_version": 2 + "extension_types": ["platform_app"] } }
diff --git a/extensions/common/api/_permission_features.json b/extensions/common/api/_permission_features.json index 9a9a665..51f353e 100644 --- a/extensions/common/api/_permission_features.json +++ b/extensions/common/api/_permission_features.json
@@ -13,8 +13,7 @@ { "alarms": { "channel": "stable", - "extension_types": ["extension", "legacy_packaged_app", "platform_app"], - "min_manifest_version": 2 + "extension_types": ["extension", "legacy_packaged_app", "platform_app"] }, "app.window.alwaysOnTop": { "channel": "stable", @@ -202,13 +201,11 @@ "declarativeNetRequest": { "channel": "stable", "extension_types": ["extension"], - "min_manifest_version": 2, "feature_flag": "DeclarativeNetRequest" }, "declarativeNetRequestFeedback": { "channel": "stable", "extension_types": ["extension"], - "min_manifest_version": 2, "feature_flag": "DeclarativeNetRequest" }, "declarativeWebRequest": { @@ -556,19 +553,16 @@ "storage": [ { "channel": "stable", - "extension_types": ["extension", "legacy_packaged_app", "platform_app"], - "min_manifest_version": 2 + "extension_types": ["extension", "legacy_packaged_app", "platform_app"] }, { "channel": "stable", "dependencies": ["behavior:imprivata_login_screen_extension"], - "extension_types": ["login_screen_extension"], - "min_manifest_version": 2 + "extension_types": ["login_screen_extension"] }, { "channel": "stable", "extension_types": ["login_screen_extension"], - "min_manifest_version": 2, "allowlist": [ "7FE4A999359A456C4B0FB7B7AD85CEA29CA50519" // Login screen APIs test extension ]
diff --git a/gpu/config/gpu_switches.cc b/gpu/config/gpu_switches.cc index cb27830..e4f60cc 100644 --- a/gpu/config/gpu_switches.cc +++ b/gpu/config/gpu_switches.cc
@@ -110,4 +110,8 @@ // TODO(crbug/1158000): Remove this switch. const char kVulkanSyncCpuMemoryLimitMb[] = "vulkan-sync-cpu-memory-limit-mb"; +// Crash Chrome if GPU process crashes. This is to force a test to fail when +// GPU process crashes unexpectedly. +const char kForceBrowserCrashOnGpuCrash[] = "force-browser-crash-on-gpu-crash"; + } // namespace switches
diff --git a/gpu/config/gpu_switches.h b/gpu/config/gpu_switches.h index 873dc80..92cfde1 100644 --- a/gpu/config/gpu_switches.h +++ b/gpu/config/gpu_switches.h
@@ -36,6 +36,7 @@ GPU_EXPORT extern const char kDisableVulkanFallbackToGLForTesting[]; GPU_EXPORT extern const char kVulkanHeapMemoryLimitMb[]; GPU_EXPORT extern const char kVulkanSyncCpuMemoryLimitMb[]; +GPU_EXPORT extern const char kForceBrowserCrashOnGpuCrash[]; } // namespace switches
diff --git a/gpu/vulkan/BUILD.gn b/gpu/vulkan/BUILD.gn index 058840e5..1de4865 100644 --- a/gpu/vulkan/BUILD.gn +++ b/gpu/vulkan/BUILD.gn
@@ -60,17 +60,12 @@ "vma_wrapper.h", ] - defines = [ - "IS_VULKAN_IMPL", - "VMA_DYNAMIC_VULKAN_FUNCTIONS=0", - "VMA_STATS_STRING_ENABLED=0", - "VMA_IMPLEMENTATION", - ] + defines = [ "IS_VULKAN_IMPL" ] deps = [ ":vulkan_function_pointers", "//base", - "//third_party/vulkan_memory_allocator", + "//third_party/vulkan_memory_allocator:vulkan_memory_allocator_with_usage", ] }
diff --git a/infra/config/generated/commit-queue.cfg b/infra/config/generated/commit-queue.cfg index ebccaec..5f1aeed 100644 --- a/infra/config/generated/commit-queue.cfg +++ b/infra/config/generated/commit-queue.cfg
@@ -1109,7 +1109,7 @@ } builders { name: "chromium/try/linux-rel-rts" - experiment_percentage: 1 + experiment_percentage: 5 location_regexp: ".*" location_regexp_exclude: ".+/[+]/docs/.+" location_regexp_exclude: ".+/[+]/infra/config/.+"
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md index 68365b1d..71d3b3c 100644 --- a/infra/config/generated/cq-builders.md +++ b/infra/config/generated/cq-builders.md
@@ -384,5 +384,5 @@ * [`//services/tracing/.+`](https://cs.chromium.org/chromium/src/services/tracing/) * [linux-rel-rts](https://ci.chromium.org/p/chromium/builders/try/linux-rel-rts) ([definition](https://cs.chromium.org/search?q=package:%5Echromium$+file:/cq.star$+-file:/beta/+-file:/stable/+linux-rel-rts)) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+linux-rel-rts)) - * Experiment percentage: 1.0 + * Experiment percentage: 5.0
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg index fc16033..dba0cd5 100644 --- a/infra/config/generated/cr-buildbucket.cfg +++ b/infra/config/generated/cr-buildbucket.cfg
@@ -42477,12 +42477,11 @@ name: "linux-rel-rts" swarming_host: "chromium-swarm.appspot.com" swarming_tags: "vpython:native-python-wrapper" - dimensions: "builderless:1" + dimensions: "builder:linux-rel-rts" dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "os:Ubuntu-16.04" dimensions: "pool:luci.chromium.try" - dimensions: "ssd:0" exe { cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master"
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star index df38182..f28a072 100644 --- a/infra/config/subprojects/chromium/try.star +++ b/infra/config/subprojects/chromium/try.star
@@ -1055,9 +1055,9 @@ try_.chromium_linux_builder( name = "linux-rel-rts", - builderless = True, + builderless = False, goma_jobs = goma.jobs.J150, - tryjob = try_.job(experiment_percentage = 1), + tryjob = try_.job(experiment_percentage = 5), use_clang_coverage = True, )
diff --git a/ios/build/tools/convert_gn_xcodeproj.py b/ios/build/tools/convert_gn_xcodeproj.py index e7ae0ca3..30932f70 100755 --- a/ios/build/tools/convert_gn_xcodeproj.py +++ b/ios/build/tools/convert_gn_xcodeproj.py
@@ -16,16 +16,36 @@ import collections import copy import filecmp -import json import hashlib +import json import os import re import shutil +import string import subprocess import sys import tempfile +class Template(string.Template): + + """A subclass of string.Template that changes delimiter.""" + + delimiter = '@' + + +def LoadSchemeTemplate(root): + """Return a string.Template object for scheme file loaded relative to root.""" + path = os.path.join(root, 'ios', 'build', 'tools', 'xcodescheme.template') + with open(path) as file: + return Template(file.read()) + + +def CreateIdentifier(str_id): + """Return a 24 characters string that can be used as an identifier.""" + return hashlib.sha1(str_id.encode("utf-8")).hexdigest()[:24].upper() + + class XcodeProject(object): def __init__(self, objects, counter = 0): @@ -36,7 +56,7 @@ while True: self.counter += 1 str_id = "%s %s %d" % (parent_name, obj['isa'], self.counter) - new_id = hashlib.sha1(str_id.encode("utf-8")).hexdigest()[:24].upper() + new_id = CreateIdentifier(str_id) # Make sure ID is unique. It's possible there could be an id conflict # since this is run after GN runs. @@ -103,6 +123,11 @@ json_data = json.loads(LoadXcodeProjectAsJSON(project_dir)) project = XcodeProject(json_data['objects']) + schemes_template = LoadSchemeTemplate(root_dir) + schemes_directory = os.path.join(project_dir, 'xcshareddata', 'xcschemes') + if not os.path.isdir(schemes_directory): + os.makedirs(schemes_directory) + objects_to_remove = [] for value in list(project.objects.values()): isa = value['isa'] @@ -136,6 +161,27 @@ value['buildConfigurations'].append( project.AddObject('products', new_build_config)) + # Create scheme files for application, extensions and framework targets. + if isa == 'PBXNativeTarget': + product_type = value['productType'] + if product_type not in ( + 'com.apple.product-type.app-extension', + 'com.apple.product-type.application', + 'com.apple.product-type.framework'): + continue + + name, product_name = (value['name'], value['productName']) + identifier = CreateIdentifier('%s, %s' % (name, product_name)) + product = project.objects[value['productReference']] + + scheme_path = os.path.join(schemes_directory, value['name'] + '.xcscheme') + with open(scheme_path, 'w') as scheme_file: + scheme_file.write( + schemes_template.substitute( + BLUEPRINT_IDENTIFIER=identifier, + PRODUCT_NAME=product['path'], + TARGET_NAME=name)) + for object_id in objects_to_remove: del project.objects[object_id]
diff --git a/ios/build/tools/setup-gn.py b/ios/build/tools/setup-gn.py index 9225b4e3..1224742 100755 --- a/ios/build/tools/setup-gn.py +++ b/ios/build/tools/setup-gn.py
@@ -262,6 +262,25 @@ if os.path.exists(temp_path): shutil.rmtree(temp_path) + # Generate an .lldbinit file for the project that load the script that fixes + # the mapping of source files (see docs/ios/build_instructions.md#debugging). + with open(os.path.join(out_dir, 'build', '.lldbinit'), 'w') as lldbinit: + lldb_script_dir = os.path.join(os.path.abspath(root_dir), 'tools', 'lldb') + lldbinit.write('script sys.path[:0] = [\'%s\']\n' % lldb_script_dir) + lldbinit.write('script import lldbinit\n') + + workspace_name = settings.getstring( + 'gn_args', + 'ios_internal_citc_workspace_name') + + if workspace_name != '': + username = os.environ['USER'] + for shortname in ('googlemac', 'third_party', 'blaze-out'): + lldbinit.write('settings append target.source-map %s %s\n' % ( + shortname, + '/google/src/cloud/%s/%s/google3/%s' % ( + username, workspace_name, shortname))) + def GenerateGnBuildRules(gn_path, root_dir, out_dir, settings): '''Generates all template configurations for gn.'''
diff --git a/ios/build/tools/xcodescheme.template b/ios/build/tools/xcodescheme.template new file mode 100644 index 0000000..3d1a0d9b --- /dev/null +++ b/ios/build/tools/xcodescheme.template
@@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1220" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}" + BuildableName = "@{PRODUCT_NAME}" + BlueprintName = "@{TARGET_NAME}" + ReferencedContainer = "container:all.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(PROJECT_DIR)/.lldbinit" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <BuildableProductRunnable + runnableDebuggingMode = "0"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}" + BuildableName = "@{PRODUCT_NAME}" + BlueprintName = "@{TARGET_NAME}" + ReferencedContainer = "container:all.xcodeproj"> + </BuildableReference> + </BuildableProductRunnable> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <BuildableProductRunnable + runnableDebuggingMode = "0"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}" + BuildableName = "@{PRODUCT_NAME}" + BlueprintName = "@{TARGET_NAME}" + ReferencedContainer = "container:all.xcodeproj"> + </BuildableReference> + </BuildableProductRunnable> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme>
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 index 2be52e9..14ca4c5 100644 --- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@ -e8542718d304c0e86b10d976f5fad98c6981d333 \ No newline at end of file +8081b4834521c54a2414dc3518b331d248ebc434 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 index 793fd84..d0dd16c8 100644 --- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@ -9aca207fc758452304da972b06960ec46d12aeb9 \ No newline at end of file +4ba1907161bc90c8978f6d45b06e65b2a4a0d81c \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 index a7c923c..cfbfd2c8 100644 --- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@ -22b0c40ee22d599443df40fc7d7eab7354720f28 \ No newline at end of file +653067a69fdb2e673ecd94368821c60dd215e851 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 index dd1b590f..220b119 100644 --- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@ -967e88ff4fe6408df162ed999f809fe60c4f9ced \ No newline at end of file +7971ede669000419bdeac77f500c176ff8f7ee02 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 index df8e7eb1..ad0cb82f 100644 --- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@ -9fdd7083dc848d421ae853078b4c9f2c333f9104 \ No newline at end of file +86de9e89777a99fed13b78029731f00315c0608b \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 index 4c755fb..d5eee18 100644 --- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@ -b2bcd55d8fa56f7e20c56dcd79df9694f58c31e4 \ No newline at end of file +6e3d5c3787856957f0d2660b27de705e4884e1f7 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 index 6afd3d2e..9843d8c 100644 --- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 +++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@ -7524514e1bca8fd9ca329deae2dc8aa9f8fc79a3 \ No newline at end of file +cfa6728e7faf4de93e0d56d2d487932806ff9ab8 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 index 05f69e4..eb46892c 100644 --- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 +++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@ -ae88a9a31696595e53cfc3e2e4398bf1aa5de560 \ No newline at end of file +08e583e294cd96605e22c92620c770e33f0a856a \ No newline at end of file
diff --git a/media/base/android/java/src/org/chromium/media/MediaCodecBridgeBuilder.java b/media/base/android/java/src/org/chromium/media/MediaCodecBridgeBuilder.java index 59aea2a..a73344b 100644 --- a/media/base/android/java/src/org/chromium/media/MediaCodecBridgeBuilder.java +++ b/media/base/android/java/src/org/chromium/media/MediaCodecBridgeBuilder.java
@@ -30,26 +30,29 @@ try { Log.i(TAG, "create MediaCodec video decoder, mime %s", mime); info = MediaCodecUtil.createDecoder(mime, codecType, mediaCrypto); + + if (info.mediaCodec == null) return null; + + MediaCodecBridge bridge = + new MediaCodecBridge(info.mediaCodec, info.bitrateAdjuster, useAsyncApi); + byte[][] csds = {csd0, csd1}; + MediaFormat format = MediaFormatBuilder.createVideoDecoderFormat(mime, width, height, + csds, hdrMetadata, info.supportsAdaptivePlayback && allowAdaptivePlayback); + + if (!bridge.configureVideo(format, surface, mediaCrypto, 0)) return null; + + if (!bridge.start()) { + bridge.release(); + return null; + } + + return bridge; } catch (Exception e) { Log.e(TAG, "Failed to create MediaCodec video decoder: %s, codecType: %d", mime, codecType, e); } - if (info.mediaCodec == null) return null; - - MediaCodecBridge bridge = - new MediaCodecBridge(info.mediaCodec, info.bitrateAdjuster, useAsyncApi); - byte[][] csds = {csd0, csd1}; - MediaFormat format = MediaFormatBuilder.createVideoDecoderFormat(mime, width, height, csds, - hdrMetadata, info.supportsAdaptivePlayback && allowAdaptivePlayback); - - if (!bridge.configureVideo(format, surface, mediaCrypto, 0)) return null; - - if (!bridge.start()) { - bridge.release(); - return null; - } - return bridge; + return null; } @CalledByNative
diff --git a/media/gpu/vaapi/test/decode.cc b/media/gpu/vaapi/test/decode.cc index ae4e596..0affb63 100644 --- a/media/gpu/vaapi/test/decode.cc +++ b/media/gpu/vaapi/test/decode.cc
@@ -41,6 +41,7 @@ " --video=<video path>\n" " [--frames=<number of frames to decode>]\n" " [--out-prefix=<path prefix of decoded frame PNGs>]\n" + " [--md5]\n" " [--loop]\n" " [--v=<log verbosity>]\n" " [--help]\n"; @@ -63,6 +64,11 @@ " If specified along with --loop (see below), only saves the first\n" " iteration of decoded frames.\n" " If omitted, the output of this binary is error or lack thereof.\n" + " --md5\n" + " Optional. If specified, prints the md5 of each decoded and\n" + " visible frame in I420 format to stdout.\n" + " Only supported when vaDeriveImage() produces I420 and NV12 data\n" + " for all frames in the video.\n" " --loop\n" " Optional. If specified, loops decoding until terminated\n" " externally or until an error occurs, at which point the current\n" @@ -195,6 +201,9 @@ dec->LastDecodedFrameToPNG( base::StringPrintf("%s_%d.png", output_prefix.c_str(), i)); } + if (cmd->HasSwitch("md5") && dec->LastDecodedFrameVisible()) { + std::cout << dec->LastDecodedFrameMD5Sum() << std::endl; + } if (++i == n_frames) break;
diff --git a/media/gpu/vaapi/test/shared_va_surface.cc b/media/gpu/vaapi/test/shared_va_surface.cc index ca866c9..b9da6cf 100644 --- a/media/gpu/vaapi/test/shared_va_surface.cc +++ b/media/gpu/vaapi/test/shared_va_surface.cc
@@ -5,6 +5,7 @@ #include <va/va.h> #include "base/files/file_util.h" +#include "base/hash/md5.h" #include "media/base/video_types.h" #include "media/gpu/vaapi/test/macros.h" #include "media/gpu/vaapi/test/shared_va_surface.h" @@ -16,6 +17,13 @@ namespace { +// Returns whether the image format |fourcc| is supported for MD5 hash checking. +// MD5 golden values are computed from vpxdec based on I420, and only certain +// format conversions are implemented. +bool IsSupportedFormat(uint32_t fourcc) { + return fourcc == VA_FOURCC_I420 || fourcc == VA_FOURCC_NV12; +} + // Derives the VAImage metadata and image data from |surface_id| in |display|, // returning true on success. bool DeriveImage(VADisplay display, @@ -26,10 +34,13 @@ VLOG_IF(2, (res != VA_STATUS_SUCCESS)) << "vaDeriveImage failed, VA error: " << vaErrorStr(res); + const uint32_t fourcc = image->format.fourcc; + DCHECK_NE(fourcc, 0u); + // TODO(jchinlee): Support derivation into 10-bit fourcc. - if (image->format.fourcc != VA_FOURCC_NV12) { + if (!IsSupportedFormat(fourcc)) { VLOG(2) << "Test decoder binary does not support derived surface format " - << "with fourcc " << media::FourccToString(image->format.fourcc); + << "with fourcc " << media::FourccToString(fourcc); res = vaDestroyImage(display, image->image_id); VA_LOG_ASSERT(res, "vaDestroyImage"); return false; @@ -40,7 +51,7 @@ return true; } -// Returns image format to use given the surface's internal VA format. +// Returns image format to fall back to given the surface's internal VA format. VAImageFormat GetImageFormat(unsigned int va_rt_format) { constexpr VAImageFormat kImageFormatNV12{.fourcc = VA_FOURCC_NV12, .byte_order = VA_LSB_FIRST, @@ -59,22 +70,15 @@ } } -// Maps the image data from |surface_id| in |display| with given |size| by -// attempting to derive into |image| and |image_data|, or creating a -// VAImage to use with vaGetImage as fallback and setting |image| and -// |image_data| accordingly. +// Retrieves the image data from |surface_id| in |display| with given |size| by +// creating a VAImage with |format| to use with vaGetImage and setting |image| +// and |image_data| accordingly. void GetSurfaceImage(VADisplay display, VASurfaceID surface_id, - unsigned int va_rt_format, + VAImageFormat format, const gfx::Size size, VAImage* image, uint8_t** image_data) { - // First attempt to derive the image from the surface. - if (DeriveImage(display, surface_id, image, image_data)) - return; - - // Fall back to getting the image with manually passed format. - VAImageFormat format = GetImageFormat(va_rt_format); VAStatus res = vaCreateImage(display, &format, size.width(), size.height(), image); VA_LOG_ASSERT(res, "vaCreateImage"); @@ -130,13 +134,15 @@ VAImage image; uint8_t* image_data; - GetSurfaceImage(va_device_.display(), id_, va_rt_format_, size_, &image, - &image_data); + // For saving as PNG and visual comparison, we just try to get the image data + // in *some* format, so use the preferred VAImageFormat. + GetSurfaceImage(va_device_.display(), id_, GetImageFormat(va_rt_format_), + size_, &image, &image_data); // Convert the image data to ARGB and write to |path|. const size_t argb_stride = image.width * 4; auto argb_data = std::make_unique<uint8_t[]>(argb_stride * image.height); - int convert_res = 0; + int convert_res = -1; const uint32_t fourcc = image.format.fourcc; DCHECK(fourcc == VA_FOURCC_NV12 || fourcc == VA_FOURCC_P010); @@ -200,5 +206,58 @@ VA_LOG_ASSERT(res, "vaDestroyImage"); } +std::string SharedVASurface::GetMD5Sum() const { + VAImage image; + uint8_t* image_data; + LOG_ASSERT(DeriveImage(va_device_.display(), id_, &image, &image_data)); + + // Golden values of MD5 sums are computed from vpxdec with packed I420 as the + // format, so convert as needed. + uint32_t luma_plane_size = + base::checked_cast<uint32_t>(image.height * image.width); + uint32_t chroma_plane_size = base::checked_cast<uint32_t>( + ((image.height + 1) / 2) * ((image.width + 1) / 2)); + std::vector<uint8_t> i420_data(luma_plane_size + 2 * chroma_plane_size, 0u); + int convert_res = -1; + const uint32_t fourcc = image.format.fourcc; + if (fourcc == VA_FOURCC_I420) { + // I420 still needs to be packed. + LOG_ASSERT(image.num_planes == 3u); + convert_res = libyuv::I420Copy( + image_data + image.offsets[0], + base::checked_cast<int>(image.pitches[0]), + image_data + image.offsets[1], + base::checked_cast<int>(image.pitches[1]), + image_data + image.offsets[2], + base::checked_cast<int>(image.pitches[2]), i420_data.data(), + base::strict_cast<int>(image.width), i420_data.data() + luma_plane_size, + base::checked_cast<int>((image.width + 1) / 2), + i420_data.data() + luma_plane_size + chroma_plane_size, + base::checked_cast<int>((image.width + 1) / 2), + base::strict_cast<int>(image.width), + base::strict_cast<int>(image.height)); + } else if (fourcc == VA_FOURCC_NV12) { + LOG_ASSERT(image.num_planes == 2u); + convert_res = libyuv::NV12ToI420( + image_data + image.offsets[0], + base::checked_cast<int>(image.pitches[0]), + image_data + image.offsets[1], + base::checked_cast<int>(image.pitches[1]), i420_data.data(), + base::strict_cast<int>(image.width), i420_data.data() + luma_plane_size, + base::checked_cast<int>((image.width + 1) / 2), + i420_data.data() + luma_plane_size + chroma_plane_size, + base::checked_cast<int>((image.width + 1) / 2), + base::strict_cast<int>(image.width), + base::strict_cast<int>(image.height)); + } + LOG_ASSERT(convert_res == 0) + << "Failed to convert " << media::FourccToString(fourcc) + << " to packed I420."; + + base::MD5Digest md5_digest; + base::MD5Sum(i420_data.data(), i420_data.size(), &md5_digest); + return MD5DigestToBase16(md5_digest); +} + } // namespace vaapi_test } // namespace media
diff --git a/media/gpu/vaapi/test/shared_va_surface.h b/media/gpu/vaapi/test/shared_va_surface.h index 8c9bfa4d..73a14ac 100644 --- a/media/gpu/vaapi/test/shared_va_surface.h +++ b/media/gpu/vaapi/test/shared_va_surface.h
@@ -33,6 +33,10 @@ // NB: vaDeriveImage may succeed but fetch garbage output in AMD. void SaveAsPNG(const std::string& path); + // Computes the MD5 sum of this surface and returns it as a human-readable hex + // string. + std::string GetMD5Sum() const; + VASurfaceID id() const { return id_; } const gfx::Size& size() const { return size_; } unsigned int va_rt_format() const { return va_rt_format_; }
diff --git a/media/gpu/vaapi/test/video_decoder.h b/media/gpu/vaapi/test/video_decoder.h index 2a70e63..2393b7d8a 100644 --- a/media/gpu/vaapi/test/video_decoder.h +++ b/media/gpu/vaapi/test/video_decoder.h
@@ -33,6 +33,13 @@ // It is therefore possible that the images outputted do not exactly match // what is displayed by playing the video stream directly. virtual void LastDecodedFrameToPNG(const std::string& path) = 0; + + // Computes the MD5 sum of the last decoded frame and returns a human-readable + // representation. + virtual std::string LastDecodedFrameMD5Sum() = 0; + + // Returns whether the last decoded frame was visible. + virtual bool LastDecodedFrameVisible() = 0; }; } // namespace vaapi_test
diff --git a/media/gpu/vaapi/test/vp9_decoder.cc b/media/gpu/vaapi/test/vp9_decoder.cc index 2a4afad..c7db541 100644 --- a/media/gpu/vaapi/test/vp9_decoder.cc +++ b/media/gpu/vaapi/test/vp9_decoder.cc
@@ -109,6 +109,7 @@ } VLOG_IF(2, !frame_hdr.show_frame) << "not displaying frame"; + last_decoded_frame_visible_ = frame_hdr.show_frame; if (frame_hdr.show_existing_frame) { last_decoded_surface_ = ref_frames_[frame_hdr.frame_to_show_map_idx]; @@ -275,5 +276,13 @@ last_decoded_surface_->SaveAsPNG(path); } +std::string Vp9Decoder::LastDecodedFrameMD5Sum() { + return last_decoded_surface_->GetMD5Sum(); +} + +bool Vp9Decoder::LastDecodedFrameVisible() { + return last_decoded_frame_visible_; +} + } // namespace vaapi_test } // namespace media
diff --git a/media/gpu/vaapi/test/vp9_decoder.h b/media/gpu/vaapi/test/vp9_decoder.h index 888e85a1..4c12cc2 100644 --- a/media/gpu/vaapi/test/vp9_decoder.h +++ b/media/gpu/vaapi/test/vp9_decoder.h
@@ -28,6 +28,8 @@ // VideoDecoder implementation. VideoDecoder::Result DecodeNextFrame() override; void LastDecodedFrameToPNG(const std::string& path) override; + std::string LastDecodedFrameMD5Sum() override; + bool LastDecodedFrameVisible() override; private: // Reads next frame from IVF stream and its size into |vp9_frame_header| and @@ -52,6 +54,9 @@ // VP9-specific data. const std::unique_ptr<Vp9Parser> vp9_parser_; std::vector<scoped_refptr<SharedVASurface>> ref_frames_; + + // Whether the last decoded frame was visible. + bool last_decoded_frame_visible_ = false; }; } // namespace vaapi_test
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc index e922cd5f..4759170 100644 --- a/media/gpu/vaapi/vaapi_wrapper.cc +++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -6,6 +6,7 @@ #include <dlfcn.h> #include <string.h> +#include <sys/types.h> #include <unistd.h> #include <va/va.h> #include <va/va_drm.h> @@ -2030,13 +2031,27 @@ } va_attrib_extbuf.num_planes = num_planes; - if (pixmap->GetDmaBufFd(0) < 0) { + const int dma_buf_fd = pixmap->GetDmaBufFd(0); + if (dma_buf_fd < 0) { LOG(ERROR) << "Failed to get dmabuf from an Ozone NativePixmap"; return nullptr; } + const off_t data_size = lseek(dma_buf_fd, /*offset=*/0, SEEK_END); + if (data_size == static_cast<off_t>(-1)) { + PLOG(ERROR) << "Failed to get the size of the dma-buf"; + return nullptr; + } + if (lseek(dma_buf_fd, /*offset=*/0, SEEK_SET) == static_cast<off_t>(-1)) { + PLOG(ERROR) << "Failed to reset the file offset of the dma-buf"; + return nullptr; + } + // If the data size doesn't fit in a uint32_t, we probably have bigger + // problems. + va_attrib_extbuf.data_size = base::checked_cast<uint32_t>(data_size); + // We only have to pass the first file descriptor to a driver. A VA-API driver // shall create a VASurface from the single fd correctly. - uintptr_t fd = base::checked_cast<uintptr_t>(pixmap->GetDmaBufFd(0)); + uintptr_t fd = base::checked_cast<uintptr_t>(dma_buf_fd); va_attrib_extbuf.buffers = &fd; va_attrib_extbuf.num_buffers = 1u;
diff --git a/media/mojo/clients/BUILD.gn b/media/mojo/clients/BUILD.gn index 994a5f1..6a4d995c 100644 --- a/media/mojo/clients/BUILD.gn +++ b/media/mojo/clients/BUILD.gn
@@ -88,6 +88,15 @@ "//ui/gl:gl", ] } + if (is_win) { + sources += [ + # TODO(frankli): s/windows/win for consistency. + "windows/media_foundation_renderer_client.cc", + "windows/media_foundation_renderer_client.h", + "windows/media_foundation_renderer_client_factory.cc", + "windows/media_foundation_renderer_client_factory.h", + ] + } } source_set("unit_tests") {
diff --git a/media/mojo/clients/mojo_renderer_factory.cc b/media/mojo/clients/mojo_renderer_factory.cc index add8975c..3b317f7 100644 --- a/media/mojo/clients/mojo_renderer_factory.cc +++ b/media/mojo/clients/mojo_renderer_factory.cc
@@ -44,6 +44,25 @@ std::move(renderer_remote)); } +#if defined(OS_WIN) +std::unique_ptr<MojoRenderer> +MojoRendererFactory::CreateMediaFoundationRenderer( + mojo::PendingReceiver<mojom::MediaFoundationRendererExtension> + renderer_extension_receiver, + const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, + VideoRendererSink* video_renderer_sink) { + DCHECK(interface_factory_); + mojo::PendingRemote<mojom::Renderer> renderer_remote; + interface_factory_->CreateMediaFoundationRenderer( + renderer_remote.InitWithNewPipeAndPassReceiver(), + std::move(renderer_extension_receiver)); + + return std::make_unique<MojoRenderer>( + media_task_runner, /*video_overlay_factory=*/nullptr, video_renderer_sink, + std::move(renderer_remote)); +} +#endif // defined(OS_WIN) + #if BUILDFLAG(ENABLE_CAST_RENDERER) std::unique_ptr<MojoRenderer> MojoRendererFactory::CreateCastRenderer( const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
diff --git a/media/mojo/clients/mojo_renderer_factory.h b/media/mojo/clients/mojo_renderer_factory.h index 2459433e..bab4060 100644 --- a/media/mojo/clients/mojo_renderer_factory.h +++ b/media/mojo/clients/mojo_renderer_factory.h
@@ -44,6 +44,14 @@ RequestOverlayInfoCB request_overlay_info_cb, const gfx::ColorSpace& target_color_space) final; +#if defined(OS_WIN) + std::unique_ptr<MojoRenderer> CreateMediaFoundationRenderer( + mojo::PendingReceiver<mojom::MediaFoundationRendererExtension> + renderer_extension_receiver, + const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, + VideoRendererSink* video_renderer_sink); +#endif // defined (OS_WIN) + #if BUILDFLAG(ENABLE_CAST_RENDERER) std::unique_ptr<MojoRenderer> CreateCastRenderer( const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
diff --git a/media/mojo/clients/windows/media_foundation_renderer_client.cc b/media/mojo/clients/windows/media_foundation_renderer_client.cc new file mode 100644 index 0000000..cb65282 --- /dev/null +++ b/media/mojo/clients/windows/media_foundation_renderer_client.cc
@@ -0,0 +1,401 @@ +// 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 "media/mojo/clients/windows/media_foundation_renderer_client.h" + +#include "base/callback_helpers.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" +#include "media/base/win/mf_helpers.h" + +namespace media { + +MediaFoundationRendererClient::MediaFoundationRendererClient( + mojo::PendingRemote<RendererExtension> renderer_extension_remote, + scoped_refptr<base::SingleThreadTaskRunner> media_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner, + std::unique_ptr<media::MojoRenderer> mojo_renderer, + media::VideoRendererSink* sink) + : mojo_renderer_(std::move(mojo_renderer)), + sink_(sink), + media_task_runner_(std::move(media_task_runner)), + compositor_task_runner_(std::move(compositor_task_runner)), + delayed_bind_renderer_extension_remote_( + std::move(renderer_extension_remote)) { + DVLOG_FUNC(1); +} + +MediaFoundationRendererClient::~MediaFoundationRendererClient() { + DVLOG_FUNC(1); + if (video_rendering_started_) { + sink_->Stop(); + } +} + +void MediaFoundationRendererClient::Initialize(MediaResource* media_resource, + RendererClient* client, + PipelineStatusCallback init_cb) { + DVLOG_FUNC(1); + DCHECK(media_task_runner_->BelongsToCurrentThread()); + DCHECK(!init_cb_); + + // Consume and bind the delayed PendingRemote now that we + // are on |media_task_runner_|. + renderer_extension_remote_.Bind( + std::move(delayed_bind_renderer_extension_remote_), media_task_runner_); + + // Handle unexpected mojo pipe disconnection such as "mf_cdm" utility process + // crashed or killed in Browser task manager. + renderer_extension_remote_.set_disconnect_handler( + base::BindOnce(&MediaFoundationRendererClient::OnConnectionError, + base::Unretained(this))); + + client_ = client; + init_cb_ = std::move(init_cb); + + const std::vector<media::DemuxerStream*> media_streams = + media_resource->GetAllStreams(); + for (const media::DemuxerStream* stream : media_streams) { + if (stream->type() == media::DemuxerStream::Type::VIDEO) { + has_video_ = true; + break; + } + } + + mojo_renderer_->Initialize( + media_resource, this, + base::BindOnce( + &MediaFoundationRendererClient::OnRemoteRendererInitialized, + weak_factory_.GetWeakPtr())); +} + +void MediaFoundationRendererClient::OnConnectionError() { + DVLOG_FUNC(1); + DCHECK(media_task_runner_->BelongsToCurrentThread()); + + if (waiting_for_dcomp_surface_handle_) { + OnReceivedRemoteDCOMPSurface(mojo::ScopedHandle()); + } +} + +void MediaFoundationRendererClient::OnRemoteRendererInitialized( + PipelineStatus status) { + DVLOG_FUNC(1) << "status=" << status; + + DCHECK(media_task_runner_->BelongsToCurrentThread()); + if (status != media::PipelineStatus::PIPELINE_OK) { + DCHECK(!init_cb_.is_null()); + std::move(init_cb_).Run(status); + return; + } + + if (has_video_) { + // TODO(frankli): Add code to init DCOMPTextureWrapper. + } else { + std::move(init_cb_).Run(status); + } +} + +void MediaFoundationRendererClient::OnDCOMPSurfaceHandleCreated(bool success) { + if (!media_task_runner_->BelongsToCurrentThread()) { + media_task_runner_->PostTask( + FROM_HERE, + base::BindOnce( + &MediaFoundationRendererClient::OnDCOMPSurfaceHandleCreated, + weak_factory_.GetWeakPtr(), success)); + return; + } + + DVLOG_FUNC(1); + DCHECK(has_video_); + + dcomp_surface_handle_bound_ = true; + return; +} + +void MediaFoundationRendererClient::OnReceivedRemoteDCOMPSurface( + mojo::ScopedHandle surface_handle) { + DVLOG_FUNC(1); + DCHECK(has_video_); + DCHECK(surface_handle.is_valid()); + DCHECK(media_task_runner_->BelongsToCurrentThread()); + + waiting_for_dcomp_surface_handle_ = false; + base::win::ScopedHandle local_handle = + mojo::UnwrapPlatformHandle(std::move(surface_handle)).TakeHandle(); + RegisterDCOMPSurfaceHandleInGPUProcess(std::move(local_handle)); +} + +void MediaFoundationRendererClient::RegisterDCOMPSurfaceHandleInGPUProcess( + base::win::ScopedHandle surface_handle) { + if (!media_task_runner_->BelongsToCurrentThread()) { + media_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&MediaFoundationRendererClient:: + RegisterDCOMPSurfaceHandleInGPUProcess, + weak_factory_.GetWeakPtr(), std::move(surface_handle))); + return; + } + + DVLOG_FUNC(1) << "surface_handle=" << surface_handle.Get(); + DCHECK(has_video_); + + mojo::ScopedHandle mojo_surface_handle = + mojo::WrapPlatformHandle(mojo::PlatformHandle(std::move(surface_handle))); + + // TODO(frankli): Pass the |mojo_surface_handle| to Gpu process. +} + +void MediaFoundationRendererClient::OnDCOMPSurfaceRegisteredInGPUProcess( + const base::UnguessableToken& token) { + DVLOG_FUNC(1); + DCHECK(has_video_); + + return; +} + +void MediaFoundationRendererClient::OnDCOMPSurfaceTextureReleased() { + DCHECK(has_video_); + return; +} + +void MediaFoundationRendererClient::OnDCOMPStreamTextureInitialized( + bool success) { + DVLOG_FUNC(1) << "success=" << success; + DCHECK(media_task_runner_->BelongsToCurrentThread()); + DCHECK(!init_cb_.is_null()); + DCHECK(has_video_); + + media::PipelineStatus status = media::PipelineStatus::PIPELINE_OK; + if (!success) { + status = media::PipelineStatus::PIPELINE_ERROR_INITIALIZATION_FAILED; + } + if (natural_size_.width() != 0 || natural_size_.height() != 0) { + InitializeDCOMPRendering(); + } + std::move(init_cb_).Run(status); +} + +void MediaFoundationRendererClient::OnVideoFrameCreated( + scoped_refptr<media::VideoFrame> video_frame) { + if (!media_task_runner_->BelongsToCurrentThread()) { + media_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&MediaFoundationRendererClient::OnVideoFrameCreated, + weak_factory_.GetWeakPtr(), video_frame)); + return; + } + + DVLOG_FUNC(1); + DCHECK(has_video_); + + video_frame->metadata().protected_video = true; + video_frame->metadata().allow_overlay = true; + + dcomp_frame_ = video_frame; + + sink_->PaintSingleFrame(dcomp_frame_, true); +} + +void MediaFoundationRendererClient::OnCompositionParamsReceived( + gfx::Rect output_rect) { + DVLOG_FUNC(1) << "output_rect=" << output_rect.ToString(); + DCHECK(media_task_runner_->BelongsToCurrentThread()); + DCHECK(has_video_); + + renderer_extension_remote_->SetOutputParams(output_rect); + return; +} + +bool MediaFoundationRendererClient::MojoSetDCOMPMode(bool enabled) { + DVLOG_FUNC(1) << "enabled=" << enabled; + DCHECK(media_task_runner_->BelongsToCurrentThread()); + DCHECK(renderer_extension_remote_.is_bound()); + + bool success = false; + if (!renderer_extension_remote_->SetDCOMPMode(enabled, &success)) { + return false; + } + return success; +} + +void MediaFoundationRendererClient::MojoGetDCOMPSurface() { + DVLOG_FUNC(1); + DCHECK(media_task_runner_->BelongsToCurrentThread()); + DCHECK(renderer_extension_remote_.is_bound()); + + if (!renderer_extension_remote_.is_connected()) { + media_task_runner_->PostTask( + FROM_HERE, + base::BindOnce( + &MediaFoundationRendererClient::OnReceivedRemoteDCOMPSurface, + weak_factory_.GetWeakPtr(), base::Passed(mojo::ScopedHandle()))); + return; + } + waiting_for_dcomp_surface_handle_ = true; + renderer_extension_remote_->GetDCOMPSurface(base::BindOnce( + &MediaFoundationRendererClient::OnReceivedRemoteDCOMPSurface, + weak_factory_.GetWeakPtr())); +} + +void MediaFoundationRendererClient::InitializeDCOMPRendering() { + DVLOG_FUNC(1); + DCHECK(has_video_); + + if (dcomp_rendering_initialized_) { + return; + } + + if (!MojoSetDCOMPMode(true)) { + DLOG(ERROR) << "Failed to initialize DCOMP mode on remote renderer. this=" + << this; + return; + } + MojoGetDCOMPSurface(); + + dcomp_rendering_initialized_ = true; + return; +} + +void MediaFoundationRendererClient::SetCdm(CdmContext* cdm_context, + CdmAttachedCB cdm_attached_cb) { + DVLOG_FUNC(1) << "cdm_context=" << cdm_context; + DCHECK(cdm_context); + + if (cdm_context_) { + DLOG(ERROR) << "Switching CDM not supported. this=" << this; + std::move(cdm_attached_cb).Run(false); + return; + } + + cdm_context_ = cdm_context; + DCHECK(cdm_attached_cb_.is_null()); + cdm_attached_cb_ = std::move(cdm_attached_cb); + mojo_renderer_->SetCdm( + cdm_context_, + base::BindOnce(&MediaFoundationRendererClient::OnCdmAttached, + weak_factory_.GetWeakPtr())); +} + +void MediaFoundationRendererClient::SetLatencyHint( + base::Optional<base::TimeDelta> /*latency_hint*/) { + // We do not use the latency hint today +} + +void MediaFoundationRendererClient::OnCdmAttached(bool success) { + DCHECK(cdm_attached_cb_); + std::move(cdm_attached_cb_).Run(success); +} + +void MediaFoundationRendererClient::Flush(base::OnceClosure flush_cb) { + mojo_renderer_->Flush(std::move(flush_cb)); +} + +void MediaFoundationRendererClient::StartPlayingFrom(base::TimeDelta time) { + mojo_renderer_->StartPlayingFrom(time); +} + +void MediaFoundationRendererClient::SetPlaybackRate(double playback_rate) { + mojo_renderer_->SetPlaybackRate(playback_rate); +} + +void MediaFoundationRendererClient::SetVolume(float volume) { + mojo_renderer_->SetVolume(volume); +} + +base::TimeDelta MediaFoundationRendererClient::GetMediaTime() { + return mojo_renderer_->GetMediaTime(); +} + +void MediaFoundationRendererClient::OnSelectedVideoTracksChanged( + const std::vector<media::DemuxerStream*>& enabled_tracks, + base::OnceClosure change_completed_cb) { + bool video_track_selected = (enabled_tracks.size() > 0); + DVLOG_FUNC(1) << "video_track_selected=" << video_track_selected; + renderer_extension_remote_->SetVideoStreamEnabled(video_track_selected); + std::move(change_completed_cb).Run(); +} + +void MediaFoundationRendererClient::OnError(PipelineStatus status) { + DVLOG_FUNC(1) << "status=" << status; + client_->OnError(status); +} + +void MediaFoundationRendererClient::OnEnded() { + client_->OnEnded(); +} + +void MediaFoundationRendererClient::OnStatisticsUpdate( + const media::PipelineStatistics& stats) { + client_->OnStatisticsUpdate(stats); +} + +void MediaFoundationRendererClient::OnBufferingStateChange( + media::BufferingState state, + media::BufferingStateChangeReason reason) { + client_->OnBufferingStateChange(state, reason); +} + +void MediaFoundationRendererClient::OnWaiting(WaitingReason reason) { + client_->OnWaiting(reason); +} + +void MediaFoundationRendererClient::OnAudioConfigChange( + const media::AudioDecoderConfig& config) { + client_->OnAudioConfigChange(config); +} +void MediaFoundationRendererClient::OnVideoConfigChange( + const media::VideoDecoderConfig& config) { + client_->OnVideoConfigChange(config); +} + +void MediaFoundationRendererClient::OnVideoNaturalSizeChange( + const gfx::Size& size) { + DVLOG_FUNC(1) << "size=" << size.ToString(); + DCHECK(media_task_runner_->BelongsToCurrentThread()); + DCHECK(has_video_); + + natural_size_ = size; + // Skip creation of a new video frame if the DCOMP surface has not yet been + // bound to the DCOMP texture as we will create a new frame after binding has + // completed. + if (dcomp_surface_handle_bound_) { + // TODO(frankli): Add code to call DCOMPTextureWrapper::CreateVideoFrame(). + } + InitializeDCOMPRendering(); + client_->OnVideoNaturalSizeChange(natural_size_); +} + +void MediaFoundationRendererClient::OnVideoOpacityChange(bool opaque) { + DVLOG_FUNC(1) << "opaque=" << opaque; + DCHECK(has_video_); + client_->OnVideoOpacityChange(opaque); +} + +void MediaFoundationRendererClient::OnVideoFrameRateChange( + base::Optional<int> fps) { + DVLOG_FUNC(1) << "fps=" << (fps ? *fps : -1); + DCHECK(has_video_); + client_->OnVideoFrameRateChange(fps); +} + +scoped_refptr<media::VideoFrame> MediaFoundationRendererClient::Render( + base::TimeTicks deadline_min, + base::TimeTicks deadline_max, + bool background_rendering) { + // Returns no video frame as it is rendered independently by Windows Direct + // Composition. + return nullptr; +} + +void MediaFoundationRendererClient::OnFrameDropped() { + return; +} + +base::TimeDelta MediaFoundationRendererClient::GetPreferredRenderInterval() { + // TODO(frankli): use 'viz::BeginFrameArgs::MinInterval()'. + return base::TimeDelta::FromSeconds(0); +} + +} // namespace media
diff --git a/media/mojo/clients/windows/media_foundation_renderer_client.h b/media/mojo/clients/windows/media_foundation_renderer_client.h new file mode 100644 index 0000000..a34ca83 --- /dev/null +++ b/media/mojo/clients/windows/media_foundation_renderer_client.h
@@ -0,0 +1,152 @@ +// 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 MEDIA_MOJO_CLIENTS_WINDOWS_MEDIA_FOUNDATION_RENDERER_CLIENT_H_ +#define MEDIA_MOJO_CLIENTS_WINDOWS_MEDIA_FOUNDATION_RENDERER_CLIENT_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/single_thread_task_runner.h" +#include "media/base/media_resource.h" +#include "media/base/renderer.h" +#include "media/base/renderer_client.h" +#include "media/base/video_renderer_sink.h" +#include "media/mojo/clients/mojo_renderer.h" +#include "media/mojo/mojom/renderer_extensions.mojom.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/receiver.h" + +namespace media { + +// MediaFoundationRendererClient lives in Renderer process and mirrors a +// MediaFoundationRenderer living in the MF_CDM LPAC Utility process. +// +// It is responsible for forwarding media::Renderer calls from WMPI to the +// MediaFoundationRenderer, using |mojo_renderer|. It also manages a +// DCOMPTexture, (via |dcomp_texture_wrapper_|) and notifies the +// VideoRendererSink when new frames are available. +// +// This class handles all calls on |media_task_runner_|, except for +// OnFrameAvailable(), which is called on |compositor_task_runner_|. +// +// N.B: This class implements media::RendererClient, in order to intercept +// OnVideoNaturalSizeChange() events, to update DCOMPTextureWrapper. All events +// (including OnVideoNaturalSizeChange()) are bubbled up to |client_|. +// +class MediaFoundationRendererClient + : public media::Renderer, + public media::RendererClient, + public media::VideoRendererSink::RenderCallback { + public: + using RendererExtension = media::mojom::MediaFoundationRendererExtension; + + MediaFoundationRendererClient( + mojo::PendingRemote<RendererExtension> renderer_extension_remote, + scoped_refptr<base::SingleThreadTaskRunner> media_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner, + std::unique_ptr<media::MojoRenderer> mojo_renderer, + media::VideoRendererSink* sink); + + ~MediaFoundationRendererClient() override; + + // media::Renderer implementation. + void Initialize(MediaResource* media_resource, + RendererClient* client, + PipelineStatusCallback init_cb) override; + void SetCdm(CdmContext* cdm_context, CdmAttachedCB cdm_attached_cb) override; + void SetLatencyHint(base::Optional<base::TimeDelta> latency_hint) override; + void Flush(base::OnceClosure flush_cb) override; + void StartPlayingFrom(base::TimeDelta time) override; + void SetPlaybackRate(double playback_rate) override; + void SetVolume(float volume) override; + base::TimeDelta GetMediaTime() override; + void OnSelectedVideoTracksChanged( + const std::vector<media::DemuxerStream*>& enabled_tracks, + base::OnceClosure change_completed_cb) override; + + // media::RendererClient implementation. + void OnError(PipelineStatus status) override; + void OnEnded() override; + void OnStatisticsUpdate(const media::PipelineStatistics& stats) override; + void OnBufferingStateChange(media::BufferingState state, + media::BufferingStateChangeReason) override; + void OnWaiting(media::WaitingReason reason) override; + void OnAudioConfigChange(const media::AudioDecoderConfig& config) override; + void OnVideoConfigChange(const media::VideoDecoderConfig& config) override; + void OnVideoNaturalSizeChange(const gfx::Size& size) override; + void OnVideoOpacityChange(bool opaque) override; + void OnVideoFrameRateChange(base::Optional<int>) override; + + // media::VideoRendererSink::RenderCallback implementation. + scoped_refptr<media::VideoFrame> Render(base::TimeTicks deadline_min, + base::TimeTicks deadline_max, + bool background_rendering) override; + void OnFrameDropped() override; + base::TimeDelta GetPreferredRenderInterval() override; + + private: + void OnConnectionError(); + void OnRemoteRendererInitialized(media::PipelineStatus status); + void OnVideoFrameCreated(scoped_refptr<media::VideoFrame> video_frame); + void OnDCOMPStreamTextureInitialized(bool success); + void OnDCOMPSurfaceTextureReleased(); + void OnDCOMPSurfaceHandleCreated(bool success); + void OnReceivedRemoteDCOMPSurface(mojo::ScopedHandle surface_handle); + void OnDCOMPSurfaceRegisteredInGPUProcess( + const base::UnguessableToken& token); + void OnCompositionParamsReceived(gfx::Rect output_rect); + + void InitializeDCOMPRendering(); + void RegisterDCOMPSurfaceHandleInGPUProcess( + base::win::ScopedHandle surface_handle); + void OnCdmAttached(bool success); + void InitializeMojoCdmTelemetryPtrServer(); + void OnCDMTelemetryPtrConnectionError(); + + bool MojoSetDCOMPMode(bool enabled); + void MojoGetDCOMPSurface(); + + // Used to forward calls to the MediaFoundationRenderer living in the MF_CDM + // LPAC Utility process. + std::unique_ptr<media::MojoRenderer> mojo_renderer_; + + RendererClient* client_ = nullptr; + + VideoRendererSink* sink_; + bool video_rendering_started_ = false; + bool dcomp_rendering_initialized_ = false; + // video's native size. + gfx::Size natural_size_; + + scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_; + scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_; + scoped_refptr<media::VideoFrame> dcomp_frame_; + bool dcomp_surface_handle_bound_ = false; + bool has_video_ = false; + + PipelineStatusCallback init_cb_; + CdmContext* cdm_context_ = nullptr; + CdmAttachedCB cdm_attached_cb_; + + // Used temporarily, to delay binding to |renderer_extension_remote_| until we + // are on the right sequence, when Initialize() is called. + mojo::PendingRemote<RendererExtension> + delayed_bind_renderer_extension_remote_; + + // Used to call methods on the MediaFoundationRenderer in the MF_CMD LPAC + // Utility process. + mojo::Remote<RendererExtension> renderer_extension_remote_; + + bool waiting_for_dcomp_surface_handle_ = false; + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<MediaFoundationRendererClient> weak_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(MediaFoundationRendererClient); +}; + +} // namespace media + +#endif // MEDIA_MOJO_CLIENTS_WINDOWS_MEDIA_FOUNDATION_RENDERER_CLIENT_H_
diff --git a/media/mojo/clients/windows/media_foundation_renderer_client_factory.cc b/media/mojo/clients/windows/media_foundation_renderer_client_factory.cc new file mode 100644 index 0000000..52f43a27 --- /dev/null +++ b/media/mojo/clients/windows/media_foundation_renderer_client_factory.cc
@@ -0,0 +1,61 @@ +// 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 "media/mojo/clients/windows/media_foundation_renderer_client_factory.h" + +#include "media/base/win/mf_helpers.h" +#include "media/mojo/clients/mojo_renderer.h" +#include "media/mojo/clients/mojo_renderer_factory.h" +#include "media/mojo/clients/windows/media_foundation_renderer_client.h" +#include "media/mojo/mojom/renderer_extensions.mojom.h" + +namespace media { + +MediaFoundationRendererClientFactory::MediaFoundationRendererClientFactory( + scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner, + std::unique_ptr<media::MojoRendererFactory> mojo_renderer_factory) + : compositor_task_runner_(std::move(compositor_task_runner)), + mojo_renderer_factory_(std::move(mojo_renderer_factory)) { + DVLOG_FUNC(1); +} + +MediaFoundationRendererClientFactory::~MediaFoundationRendererClientFactory() { + DVLOG_FUNC(1); +} + +std::unique_ptr<media::Renderer> +MediaFoundationRendererClientFactory::CreateRenderer( + const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, + const scoped_refptr<base::TaskRunner>& /*worker_task_runner*/, + media::AudioRendererSink* /*audio_renderer_sink*/, + media::VideoRendererSink* video_renderer_sink, + media::RequestOverlayInfoCB /*request_overlay_info_cb*/, + const gfx::ColorSpace& /*target_color_space*/) { + DVLOG_FUNC(1); + + // Used to send messages from the MediaFoundationRendererClient (Renderer + // process), to the MediaFoundationRenderer (MF_CDM LPAC Utility process). + // The |renderer_extension_receiver| will be bound in MediaFoundationRenderer. + mojo::PendingRemote<media::mojom::MediaFoundationRendererExtension> + renderer_extension_remote; + auto renderer_extension_receiver = + renderer_extension_remote.InitWithNewPipeAndPassReceiver(); + + std::unique_ptr<media::MojoRenderer> mojo_renderer = + mojo_renderer_factory_->CreateMediaFoundationRenderer( + std::move(renderer_extension_receiver), media_task_runner, + video_renderer_sink); + + // mojo_renderer's ownership is passed to MediaFoundationRendererClient. + return std::make_unique<MediaFoundationRendererClient>( + std::move(renderer_extension_remote), media_task_runner, + compositor_task_runner_, std::move(mojo_renderer), video_renderer_sink); +} + +media::MediaResource::Type +MediaFoundationRendererClientFactory::GetRequiredMediaResourceType() { + return media::MediaResource::Type::STREAM; +} + +} // namespace media \ No newline at end of file
diff --git a/media/mojo/clients/windows/media_foundation_renderer_client_factory.h b/media/mojo/clients/windows/media_foundation_renderer_client_factory.h new file mode 100644 index 0000000..3117a5f7 --- /dev/null +++ b/media/mojo/clients/windows/media_foundation_renderer_client_factory.h
@@ -0,0 +1,45 @@ +// 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 MEDIA_MOJO_CLIENTS_WINDOWS_MEDIA_FOUNDATION_RENDERER_CLIENT_FACTORY_H_ +#define MEDIA_MOJO_CLIENTS_WINDOWS_MEDIA_FOUNDATION_RENDERER_CLIENT_FACTORY_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/single_thread_task_runner.h" +#include "media/base/renderer_factory.h" +#include "media/mojo/clients/mojo_renderer_factory.h" +#include "mojo/public/cpp/bindings/interface_request.h" + +namespace media { + +// The default class for creating a MediaFoundationRendererClient +// and its associated MediaFoundationRenderer. +class MediaFoundationRendererClientFactory : public media::RendererFactory { + public: + MediaFoundationRendererClientFactory( + scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner, + std::unique_ptr<media::MojoRendererFactory> mojo_renderer_factory); + ~MediaFoundationRendererClientFactory() override; + + std::unique_ptr<media::Renderer> CreateRenderer( + const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, + const scoped_refptr<base::TaskRunner>& worker_task_runner, + media::AudioRendererSink* audio_renderer_sink, + media::VideoRendererSink* video_renderer_sink, + media::RequestOverlayInfoCB request_surface_cb, + const gfx::ColorSpace& target_color_space) override; + + // The MediaFoundationRenderer uses a Type::URL. + media::MediaResource::Type GetRequiredMediaResourceType() override; + + private: + scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_; + + std::unique_ptr<media::MojoRendererFactory> mojo_renderer_factory_; +}; + +} // namespace media + +#endif // MEDIA_MOJO_CLIENTS_WINDOWS_MEDIA_FOUNDATION_RENDERER_CLIENT_FACTORY_H_
diff --git a/media/mojo/mojom/interface_factory.mojom b/media/mojo/mojom/interface_factory.mojom index b478762..09e7328 100644 --- a/media/mojo/mojom/interface_factory.mojom +++ b/media/mojo/mojom/interface_factory.mojom
@@ -46,6 +46,14 @@ pending_receiver<Renderer> renderer, pending_receiver<MediaPlayerRendererExtension> renderer_extension); + [EnableIf=is_win] + // Creates a MediaFoundationRenderer (MediaFoundationRendererClientFactory). + // - |renderer_extension| is bound in MediaFoundationRenderer, and receives + // calls from MediaFoundationRendererClient. + CreateMediaFoundationRenderer( + pending_receiver<Renderer> renderer, + pending_receiver<MediaFoundationRendererExtension> renderer_extension); + [EnableIf=is_android] // Creates a FlingingRenderer (FlingingRendererClientFactory). // The |presentation_id| is used to find an already set-up RemotePlayback
diff --git a/media/mojo/mojom/renderer_extensions.mojom b/media/mojo/mojom/renderer_extensions.mojom index 2bd84f4..bd245a41 100644 --- a/media/mojo/mojom/renderer_extensions.mojom +++ b/media/mojo/mojom/renderer_extensions.mojom
@@ -47,3 +47,28 @@ // network). OnRemotePlayStateChange(MediaStatusState state); }; + +[EnableIf=is_win] +// Extension of the mojo::RendererClient communication layer for MF-based CDM +// Renderer. +// This allows the media::Renderer from Renderer process calling into the +// MediaFoundationRenderer in the "mf_cdm" sandbox'ed Utility process. +// Concretely, the MediaFoundationRendererClient uses these methods to send +// commands to MediaFoundationRenderer, which lives in the mf_cdm LPAC-based +// Utility process. +// Please refer to media/renderers/win/media_foundation_renderer_extension.h +// for its C++ interface equivalence. +interface MediaFoundationRendererExtension { + // Enable Direct Composition video rendering. + [Sync] + SetDCOMPMode(bool enabled) => (bool succeeded); + + // Get a Direct Composition Surface handle. + GetDCOMPSurface() => (handle? dcomp_surface); + + // Notify renderer whether video is enabled. + SetVideoStreamEnabled(bool enabled); + + // Notify renderer of output composition parameters + SetOutputParams(gfx.mojom.Rect rect); +};
diff --git a/media/mojo/services/BUILD.gn b/media/mojo/services/BUILD.gn index 44a9274..80f3ef75 100644 --- a/media/mojo/services/BUILD.gn +++ b/media/mojo/services/BUILD.gn
@@ -145,6 +145,14 @@ deps += [ "//chromeos/components/cdm_factory_daemon:cdm_factory_daemon_gpu" ] } + + if (is_win) { + sources += [ + "media_foundation_renderer_wrapper.cc", + "media_foundation_renderer_wrapper.h", + ] + deps += [ "//media/base/win:media_foundation_util" ] + } } source_set("unit_tests") {
diff --git a/media/mojo/services/interface_factory_impl.cc b/media/mojo/services/interface_factory_impl.cc index 65731303..d5297d4 100644 --- a/media/mojo/services/interface_factory_impl.cc +++ b/media/mojo/services/interface_factory_impl.cc
@@ -147,6 +147,21 @@ } #endif // defined(OS_ANDROID) +#if defined(OS_WIN) +void InterfaceFactoryImpl::CreateMediaFoundationRenderer( + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) { + DVLOG(1) << __func__ << ": this=" << this; + + scoped_refptr<base::SingleThreadTaskRunner> task_runner = + base::ThreadTaskRunnerHandle::Get(); + CreateMediaFoundationRendererOnTaskRunner( + std::move(task_runner), std::move(receiver), + std::move(renderer_extension_receiver)); +} +#endif // defined (OS_WIN) + void InterfaceFactoryImpl::CreateCdm(const std::string& key_system, const CdmConfig& cdm_config, CreateCdmCallback callback) { @@ -266,4 +281,29 @@ #endif // BUILDFLAG(ENABLE_MOJO_CDM) +#if defined(OS_WIN) +void InterfaceFactoryImpl::CreateMediaFoundationRendererOnTaskRunner( + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) { + DVLOG(1) << __func__ << ": this=" << this; + + if (!task_runner->RunsTasksInCurrentSequence()) { + task_runner->PostTask( + FROM_HERE, + base::BindOnce( + &InterfaceFactoryImpl::CreateMediaFoundationRendererOnTaskRunner, + base::Unretained(this), std::move(task_runner), std::move(receiver), + std::move(renderer_extension_receiver))); + return; + } + + DVLOG(1) << __func__ << ": this=" << this; + + // TODO(frankli): Invoke media::MojoRendererService::Create() with our + // specific parameters. +} +#endif // defined(OS_WIN) + } // namespace media
diff --git a/media/mojo/services/interface_factory_impl.h b/media/mojo/services/interface_factory_impl.h index c8341e6a..be55cc5 100644 --- a/media/mojo/services/interface_factory_impl.h +++ b/media/mojo/services/interface_factory_impl.h
@@ -68,6 +68,13 @@ client_extension, mojo::PendingReceiver<mojom::Renderer> receiver) final; #endif // defined(OS_ANDROID) +#if defined(OS_WIN) + void CreateMediaFoundationRenderer( + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) final; +#endif // defined(OS_WIN) + void CreateCdm(const std::string& key_system, const CdmConfig& cdm_config, CreateCdmCallback callback) final; @@ -91,6 +98,14 @@ const std::string& error_message); #endif // BUILDFLAG(ENABLE_MOJO_CDM) +#if defined(OS_WIN) + void CreateMediaFoundationRendererOnTaskRunner( + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver); +#endif // defined(OS_WIN) + // Must be declared before the receivers below because the bound objects might // take a raw pointer of |cdm_service_context_| and assume it's always // available.
diff --git a/media/mojo/services/media_foundation_renderer_wrapper.cc b/media/mojo/services/media_foundation_renderer_wrapper.cc new file mode 100644 index 0000000..21953a2 --- /dev/null +++ b/media/mojo/services/media_foundation_renderer_wrapper.cc
@@ -0,0 +1,101 @@ +// 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 "media/mojo/services/media_foundation_renderer_wrapper.h" + +#include "base/callback_helpers.h" +#include "media/base/win/mf_helpers.h" +#include "media/mojo/mojom/renderer_extensions.mojom.h" +#include "mojo/public/cpp/system/platform_handle.h" + +namespace media { + +MediaFoundationRendererWrapper::MediaFoundationRendererWrapper( + bool web_contents_muted, + scoped_refptr<base::SequencedTaskRunner> task_runner, + mojo::PendingReceiver<RendererExtension> renderer_extension_receiver) + : renderer_(std::make_unique<media::MediaFoundationRenderer>( + web_contents_muted, + std::move(task_runner))), + renderer_extension_receiver_(this, + std::move(renderer_extension_receiver)) { + DVLOG_FUNC(1); +} + +MediaFoundationRendererWrapper::~MediaFoundationRendererWrapper() { + DVLOG_FUNC(1); +} + +void MediaFoundationRendererWrapper::Initialize( + media::MediaResource* media_resource, + media::RendererClient* client, + media::PipelineStatusCallback init_cb) { + renderer_->Initialize(media_resource, client, std::move(init_cb)); +} + +void MediaFoundationRendererWrapper::SetCdm(CdmContext* cdm_context, + CdmAttachedCB cdm_attached_cb) { + renderer_->SetCdm(cdm_context, std::move(cdm_attached_cb)); +} + +void MediaFoundationRendererWrapper::SetLatencyHint( + base::Optional<base::TimeDelta> latency_hint) { + renderer_->SetLatencyHint(latency_hint); +} + +void MediaFoundationRendererWrapper::Flush(base::OnceClosure flush_cb) { + renderer_->Flush(std::move(flush_cb)); +} + +void MediaFoundationRendererWrapper::StartPlayingFrom(base::TimeDelta time) { + renderer_->StartPlayingFrom(time); +} + +void MediaFoundationRendererWrapper::SetPlaybackRate(double playback_rate) { + renderer_->SetPlaybackRate(playback_rate); +} + +void MediaFoundationRendererWrapper::SetVolume(float volume) { + return renderer_->SetVolume(volume); +} + +base::TimeDelta MediaFoundationRendererWrapper::GetMediaTime() { + return renderer_->GetMediaTime(); +} + +void MediaFoundationRendererWrapper::SetDCOMPMode( + bool enabled, + SetDCOMPModeCallback callback) { + renderer_->SetDCompMode(enabled, std::move(callback)); +} + +void MediaFoundationRendererWrapper::GetDCOMPSurface( + GetDCOMPSurfaceCallback callback) { + get_decomp_surface_cb_ = std::move(callback); + renderer_->GetDCompSurface( + base::BindOnce(&MediaFoundationRendererWrapper::OnReceiveDCOMPSurface, + weak_factory_.GetWeakPtr())); +} + +void MediaFoundationRendererWrapper::SetVideoStreamEnabled(bool enabled) { + renderer_->SetVideoStreamEnabled(enabled); +} + +void MediaFoundationRendererWrapper::SetOutputParams( + const gfx::Rect& output_rect) { + renderer_->SetOutputParams(output_rect); +} + +void MediaFoundationRendererWrapper::OnReceiveDCOMPSurface(HANDLE handle) { + base::win::ScopedHandle local_surface_handle; + local_surface_handle.Set(handle); + if (get_decomp_surface_cb_) { + mojo::ScopedHandle surface_handle; + surface_handle = mojo::WrapPlatformHandle( + mojo::PlatformHandle(std::move(local_surface_handle))); + std::move(get_decomp_surface_cb_).Run(std::move(surface_handle)); + } +} + +} // namespace media
diff --git a/media/mojo/services/media_foundation_renderer_wrapper.h b/media/mojo/services/media_foundation_renderer_wrapper.h new file mode 100644 index 0000000..01f8fa0e --- /dev/null +++ b/media/mojo/services/media_foundation_renderer_wrapper.h
@@ -0,0 +1,70 @@ +// 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 MEDIA_MOJO_SERVICES_MEDIA_FOUNDATION_RENDERER_WRAPPER_H_ +#define MEDIA_MOJO_SERVICES_MEDIA_FOUNDATION_RENDERER_WRAPPER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "media/base/media_resource.h" +#include "media/base/pipeline_status.h" +#include "media/base/renderer.h" +#include "media/base/renderer_client.h" +#include "media/mojo/mojom/renderer_extensions.mojom.h" +#include "media/renderers/win/media_foundation_renderer.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/receiver.h" + +namespace media { + +// Wrap media::MediaFoundationRenderer to remove its dependence on +// media::mojom::MediaFoundationRendererExtension interface. +// +class MediaFoundationRendererWrapper + : public media::Renderer, + public media::mojom::MediaFoundationRendererExtension { + public: + using RendererExtension = media::mojom::MediaFoundationRendererExtension; + + MediaFoundationRendererWrapper( + bool web_contents_muted, + scoped_refptr<base::SequencedTaskRunner> task_runner, + mojo::PendingReceiver<RendererExtension> renderer_extension_receiver); + + ~MediaFoundationRendererWrapper() final; + + // media::Renderer implementation. + void Initialize(media::MediaResource* media_resource, + media::RendererClient* client, + media::PipelineStatusCallback init_cb) override; + void SetCdm(CdmContext* cdm_context, CdmAttachedCB cdm_attached_cb) override; + void SetLatencyHint(base::Optional<base::TimeDelta> latency_hint) override; + void Flush(base::OnceClosure flush_cb) override; + void StartPlayingFrom(base::TimeDelta time) override; + void SetPlaybackRate(double playback_rate) override; + void SetVolume(float volume) override; + base::TimeDelta GetMediaTime() override; + + // media::mojom::MediaFoundationRendererExtension implementation. + void SetDCOMPMode(bool enabled, SetDCOMPModeCallback callback) final; + void GetDCOMPSurface(GetDCOMPSurfaceCallback callback) final; + void SetVideoStreamEnabled(bool enabled) final; + void SetOutputParams(const gfx::Rect& output_rect) final; + + private: + void OnReceiveDCOMPSurface(HANDLE handle); + + std::unique_ptr<media::MediaFoundationRenderer> renderer_; + mojo::Receiver<MediaFoundationRendererExtension> renderer_extension_receiver_; + GetDCOMPSurfaceCallback get_decomp_surface_cb_; + + base::WeakPtrFactory<MediaFoundationRendererWrapper> weak_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(MediaFoundationRendererWrapper); +}; + +} // namespace media + +#endif // MEDIA_MOJO_SERVICES_MEDIA_FOUNDATION_RENDERER_WRAPPER_H_
diff --git a/media/renderers/audio_renderer_impl.cc b/media/renderers/audio_renderer_impl.cc index 0a2905e..9435558 100644 --- a/media/renderers/audio_renderer_impl.cc +++ b/media/renderers/audio_renderer_impl.cc
@@ -727,7 +727,7 @@ void AudioRendererImpl::SetVolume(float volume) { DCHECK(task_runner_->BelongsToCurrentThread()); - was_unmuted_ = was_unmuted_ || volume != 0; + was_unmuted_ = was_unmuted_ || volume != 0; if (state_ == kUninitialized || state_ == kInitializing) { volume_ = volume; return;
diff --git a/media/renderers/win/media_foundation_renderer.cc b/media/renderers/win/media_foundation_renderer.cc index fd6c928..7c47302 100644 --- a/media/renderers/win/media_foundation_renderer.cc +++ b/media/renderers/win/media_foundation_renderer.cc
@@ -73,7 +73,7 @@ scoped_refptr<base::SequencedTaskRunner> task_runner, bool force_dcomp_mode_for_testing) : muted_(muted), - task_runner_(task_runner), + task_runner_(std::move(task_runner)), force_dcomp_mode_for_testing_(force_dcomp_mode_for_testing) { DVLOG_FUNC(1); }
diff --git a/mojo/core/BUILD.gn b/mojo/core/BUILD.gn index ae3c1b23..aeb77dd 100644 --- a/mojo/core/BUILD.gn +++ b/mojo/core/BUILD.gn
@@ -105,7 +105,6 @@ public_deps = [ "//base", - "//mojo/core/embedder:features", "//mojo/core/ports", "//mojo/public/c/system:headers", "//mojo/public/cpp/platform", @@ -125,15 +124,6 @@ "channel_posix.h", ] } - - if ((is_linux || is_chromeos || is_android) && !is_nacl) { - sources += [ - "channel_linux.cc", - "channel_linux.h", - ] - - public += [ "channel_linux.h" ] - } } if (is_mac) {
diff --git a/mojo/core/channel.cc b/mojo/core/channel.cc index 8609404..4fe42510 100644 --- a/mojo/core/channel.cc +++ b/mojo/core/channel.cc
@@ -480,7 +480,9 @@ data_ = MakeAlignedBuffer(size_); } - ~ReadBuffer() { DCHECK(data_); } + ~ReadBuffer() { + DCHECK(data_); + } const char* occupied_bytes() const { return data_.get() + num_discarded_bytes_; @@ -727,19 +729,5 @@ return false; } -// Currently only Non-nacl CrOs, Linux, and Android support upgrades. -#if defined(OS_NACL) || \ - (!(defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_ANDROID))) -// static -MOJO_SYSTEM_IMPL_EXPORT bool Channel::SupportsChannelUpgrade() { - return false; -} - -MOJO_SYSTEM_IMPL_EXPORT void Channel::OfferChannelUpgrade() { - NOTREACHED(); - return; -} -#endif - } // namespace core } // namespace mojo
diff --git a/mojo/core/channel.h b/mojo/core/channel.h index 6e672f05..3b01b39 100644 --- a/mojo/core/channel.h +++ b/mojo/core/channel.h
@@ -76,16 +76,6 @@ #endif // A normal message that uses Header and can contain extra header values. NORMAL, - - // The UPGRADE_OFFER control message offers to upgrade the channel to - // another side who has advertised support for an upgraded channel. - UPGRADE_OFFER, - // The UPGRADE_ACCEPT control message is returned when an upgrade offer is - // accepted. - UPGRADE_ACCEPT, - // The UPGRADE_REJECT control message is returned when the receiver cannot - // or chooses not to upgrade the channel. - UPGRADE_REJECT, }; #pragma pack(push, 1) @@ -291,15 +281,7 @@ #if defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_MAC) // At this point only ChannelPosix needs InitFeatures. static void set_posix_use_writev(bool use_writev); -#endif // defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_MAC) - - // SupportsChannelUpgrade will return true if this channel is capable of being - // upgraded. - static bool SupportsChannelUpgrade(); - - // OfferChannelUpgrade will inform this channel that it should offer an - // upgrade to the remote. - void OfferChannelUpgrade(); +#endif // Allows the caller to change the Channel's HandlePolicy after construction. void set_handle_policy(HandlePolicy policy) { handle_policy_ = policy; } @@ -346,9 +328,6 @@ Delegate* delegate() const { return delegate_; } - // Allows the caller to determine the current HandlePolicy. - HandlePolicy handle_policy() const { return handle_policy_; } - // Called by the implementation when it wants somewhere to stick data. // |*buffer_capacity| may be set by the caller to indicate the desired buffer // size. If 0, a sane default size will be used instead.
diff --git a/mojo/core/channel_linux.cc b/mojo/core/channel_linux.cc deleted file mode 100644 index 222cba8..0000000 --- a/mojo/core/channel_linux.cc +++ /dev/null
@@ -1,869 +0,0 @@ -// 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. - -#include "mojo/core/channel_linux.h" - -#include <fcntl.h> -#include <linux/futex.h> -#include <linux/memfd.h> -#include <sys/eventfd.h> -#include <sys/mman.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include <algorithm> -#include <atomic> -#include <cstring> -#include <limits> -#include <memory> - -#include "base/bind.h" -#include "base/callback.h" -#include "base/files/scoped_file.h" -#include "base/location.h" -#include "base/logging.h" -#include "base/macros.h" -#include "base/memory/ptr_util.h" -#include "base/memory/ref_counted.h" -#include "base/memory/shared_memory_security_policy.h" -#include "base/message_loop/message_pump_for_io.h" -#include "base/metrics/histogram_macros.h" -#include "base/posix/eintr_wrapper.h" -#include "base/task_runner.h" -#include "base/time/time.h" -#include "build/build_config.h" -#include "mojo/core/core.h" -#include "mojo/core/embedder/features.h" - -namespace mojo { -namespace core { - -// DataAvailableNotifier is a simple interface which allows us to -// substitute how we notify the reader that we've made data available, -// implementations might be EventFDNotifier or FutexNotifier. -class DataAvailableNotifier { - public: - DataAvailableNotifier() = default; - explicit DataAvailableNotifier(base::RepeatingClosure callback) - : callback_(std::move(callback)) {} - - virtual ~DataAvailableNotifier() = default; - - // The writer should notify the reader by invoking Notify. - virtual bool Notify() = 0; - - // A reader should clear the notification (if appropriate) by calling Clear. - virtual bool Clear() = 0; - - // Is_valid will return true if the implementation is valid and can be used. - virtual bool is_valid() const = 0; - - protected: - // DataAvailable will be called by implementations of DataAvailableNotifier to - // dispatch this message into the registered callback. - void DataAvailable() { - DCHECK(callback_); - callback_.Run(); - } - - base::RepeatingClosure callback_; -}; - -namespace { - -constexpr int kMemFDSeals = F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW; - -std::atomic_bool g_params_set{false}; -std::atomic_bool g_use_shared_mem{false}; -std::atomic_uint32_t g_shared_mem_pages{4}; - -struct UpgradeOfferMessage { - constexpr static int kEventFdNotifier = 1; - constexpr static int kSupportedVersion = kEventFdNotifier; - - constexpr static int kDefaultPages = 4; - - int version = kSupportedVersion; - int num_pages = kDefaultPages; -}; - -constexpr size_t RoundUpToWordBoundary(size_t size) { - return (size + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1); -} - -base::ScopedFD CreateSealedMemFD(size_t size) { - CHECK_GT(size, 0u); - CHECK_EQ(size % base::GetPageSize(), 0u); - base::ScopedFD fd(syscall(__NR_memfd_create, "mojo_channel_linux", - MFD_CLOEXEC | MFD_ALLOW_SEALING)); - if (!fd.is_valid()) { - PLOG(ERROR) << "Unable to create memfd for shared memory channel"; - return {}; - } - - if (ftruncate(fd.get(), size) < 0) { - PLOG(ERROR) << "Unable to truncate memfd for shared memory channel"; - return {}; - } - - // We make sure to use F_SEAL_SEAL to prevent any further changes to the - // seals and F_SEAL_SHRINK guarantees that we won't accidentally decrease - // the size, and similarly F_SEAL_GROW for increasing size. - if (fcntl(fd.get(), F_ADD_SEALS, kMemFDSeals) < 0) { - PLOG(ERROR) << "Unable to seal memfd for shared memory channel"; - return {}; - } - - return fd; -} - -// It's very important that we always verify that the FD we're passing and the -// FD we're receive is a properly sealed MemFD. -bool ValidateFDIsProperlySealedMemFD(const base::ScopedFD& fd) { - int seals = 0; - if ((seals = fcntl(fd.get(), F_GET_SEALS)) < 0) { - PLOG(ERROR) << "Unable to get seals on memfd for shared memory channel"; - return false; - } - - return seals == kMemFDSeals; -} - -// EventFDNotifier is an implementation of the DataAvailableNotifier interface -// which uses EventFDNotifier to signal the reader. -class EventFDNotifier : public DataAvailableNotifier, - public base::MessagePumpForIO::FdWatcher { - public: - EventFDNotifier(EventFDNotifier&& efd) = default; - ~EventFDNotifier() override { reset(); } - - static constexpr int kEfdFlags = EFD_CLOEXEC | EFD_NONBLOCK; - - static std::unique_ptr<EventFDNotifier> CreateWriteNotifier() { - int fd = eventfd(0, kEfdFlags); - if (fd < 0) { - PLOG(ERROR) << "Unable to create an eventfd"; - return nullptr; - } - - return WrapFD(base::ScopedFD(fd)); - } - - // The EventFD read notifier MUST be created on the IOThread. Luckily you're - // typically creating the read notifier in response to an OFFER_UPGRADE - // message which was received on the IOThread. - static std::unique_ptr<EventFDNotifier> CreateReadNotifier( - base::ScopedFD efd, - base::RepeatingClosure cb, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) { - DCHECK(io_task_runner->RunsTasksInCurrentSequence()); - DCHECK(cb); - - return WrapFDWithCallback(std::move(efd), std::move(cb), io_task_runner); - } - - static bool KernelSupported() { - // Try to create an eventfd with bad flags if we get -EINVAL it's supported - // if we get -ENOSYS it's not, we also support -EPERM because seccomp - // policies can cause it to be returned. - int ret = eventfd(0, ~0); - PCHECK(ret < 0 && (errno == EINVAL || errno == ENOSYS || errno == EPERM)); - return (ret < 0 && errno == EINVAL); - } - - // DataAvailableNotifier impl: - bool Clear() override { - uint64_t value = 0; - ssize_t res = HANDLE_EINTR( - read(fd_.get(), reinterpret_cast<void*>(&value), sizeof(value))); - if (res < static_cast<int64_t>(sizeof(value))) { - PLOG_IF(ERROR, errno != EWOULDBLOCK) << "eventfd read error"; - } - return res == sizeof(value); - } - - bool Notify() override { - uint64_t value = 1; - ssize_t res = HANDLE_EINTR(write(fd_.get(), &value, sizeof(value))); - return res == sizeof(value); - } - - bool is_valid() const override { return fd_.is_valid(); } - - // base::MessagePumpForIO::FdWatcher impl: - void OnFileCanReadWithoutBlocking(int fd) override { - DCHECK(fd == fd_.get()); - - // Invoke the callback to inform them that data is available to read. - DataAvailable(); - } - - void OnFileCanWriteWithoutBlocking(int fd) override {} - - base::ScopedFD take() { return std::move(fd_); } - base::ScopedFD take_dup() { - return base::ScopedFD(HANDLE_EINTR(dup(fd_.get()))); - } - - void reset() { - watcher_.reset(); - fd_.reset(); - } - - int fd() { return fd_.get(); } - - private: - explicit EventFDNotifier(base::ScopedFD fd) : fd_(std::move(fd)) {} - explicit EventFDNotifier( - base::ScopedFD fd, - base::RepeatingClosure cb, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) - : DataAvailableNotifier(std::move(cb)), - fd_(std::move(fd)), - io_task_runner_(io_task_runner) { - DCHECK(watcher_); - watcher_ = - std::make_unique<base::MessagePumpForIO::FdWatchController>(FROM_HERE); - WaitForEventFDOnIOThread(); - } - - static std::unique_ptr<EventFDNotifier> WrapFD(base::ScopedFD fd) { - return base::WrapUnique<EventFDNotifier>( - new EventFDNotifier(std::move(fd))); - } - - static std::unique_ptr<EventFDNotifier> WrapFDWithCallback( - base::ScopedFD fd, - base::RepeatingClosure cb, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) { - return base::WrapUnique<EventFDNotifier>( - new EventFDNotifier(std::move(fd), std::move(cb), io_task_runner)); - } - - void WaitForEventFDOnIOThread() { - DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); - base::CurrentIOThread::Get()->WatchFileDescriptor( - fd_.get(), true, base::MessagePumpForIO::WATCH_READ, watcher_.get(), - this); - } - - base::ScopedFD fd_; - std::unique_ptr<base::MessagePumpForIO::FdWatchController> watcher_; - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; - - DISALLOW_COPY_AND_ASSIGN(EventFDNotifier); -}; - -} // namespace - -// SharedBuffer is an abstraction around a region of shared memory, it has -// methods to facilitate safely reading and writing into the shared region. -// SharedBuffer only handles the access to the shared memory any notifications -// must be performed separately. -class ChannelLinux::SharedBuffer { - public: - SharedBuffer(SharedBuffer&& other) = default; - ~SharedBuffer() { reset(); } - - enum class Error { kSuccess = 0, kGeneralError = 1, kControlCorruption = 2 }; - - static std::unique_ptr<SharedBuffer> Create(const base::ScopedFD& memfd, - size_t size) { - if (!memfd.is_valid()) { - return nullptr; - } - - // Enforce the system shared memory security policy. - if (!base::SharedMemorySecurityPolicy::AcquireReservationForMapping(size)) { - LOG(ERROR) - << "Unable to create shared buffer: unable to acquire reservation"; - return nullptr; - } - - uint8_t* ptr = reinterpret_cast<uint8_t*>( - mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, - memfd.get(), 0)); - - if (ptr == MAP_FAILED) { - PLOG(ERROR) << "Unable to map shared memory"; - - // Always clean up our reservation if we actually fail to map. - base::SharedMemorySecurityPolicy::ReleaseReservationForMapping(size); - return nullptr; - } - - return base::WrapUnique<SharedBuffer>(new SharedBuffer(ptr, size)); - } - - uint8_t* usable_region_ptr() const { return base_ptr_ + kReservedSpace; } - size_t usable_len() const { return len_ - kReservedSpace; } - bool is_valid() const { return base_ptr_ != nullptr && len_ > 0; } - - void reset() { - if (is_valid()) { - if (munmap(base_ptr_, len_) < 0) { - PLOG(ERROR) << "Unable to unmap shared buffer"; - return; - } - - base::SharedMemorySecurityPolicy::ReleaseReservationForMapping(len_); - base_ptr_ = nullptr; - len_ = 0; - } - } - - // Only one side should call Initialize, this will initialize the first - // sizeof(ControlStructure) bytes as our control structure. This should be - // done when offering fast comms. - void Initialize() { new (static_cast<void*>(base_ptr_)) ControlStructure; } - - // TryWrite will attempt to append |data| of |len| to the shared buffer, this - // call will only succeed if there is no one else trying to write AND there is - // enough space currently in the buffer. - Error TryWrite(const void* data, size_t len) { - DCHECK(data); - DCHECK(len); - - if (len > usable_len()) { - UMA_HISTOGRAM_COUNTS_100000( - "Mojo.Channel.Linux.SharedMemWriteBytes_Fail_TooLarge", len); - return Error::kGeneralError; - } - - if (!TryLockForWriting()) { - UMA_HISTOGRAM_COUNTS_100000( - "Mojo.Channel.Linux.SharedMemWriteBytes_Fail_NoLock", len); - return Error::kGeneralError; - } - - // At this point we know that the space available can only grow because - // we're the only writer we will write from write_pos -> end and 0 -> (len - // - (end - write_pos)) where end is usable_len(). - uint32_t cur_read_pos = read_pos().load(); - uint32_t cur_write_pos = write_pos().load(); - - if (!ValidateReadWritePositions(cur_read_pos, cur_write_pos)) { - UnlockForWriting(); - return Error::kControlCorruption; - } - - uint32_t space_available = - usable_len() - NumBytesInUse(cur_read_pos, cur_write_pos); - - if (space_available <= len) { - UnlockForWriting(); - UMA_HISTOGRAM_COUNTS_100000( - "Mojo.Channel.Linux.SharedMemWriteBytes_Fail_NoSpace", len); - - return Error::kGeneralError; - } - - // If we do not have enough space from the current write position to the end - // then we will be forced to wrap around. If we do have enough space we can - // just start writing at the write position, otherwise we start writing at - // the write position up to the end of the usable area and then we write the - // remainder of the payload starting at position 0. - if ((usable_len() - cur_write_pos) > len) { - memcpy(usable_region_ptr() + cur_write_pos, data, len); - } else { - size_t copy1_len = usable_len() - cur_write_pos; - memcpy(usable_region_ptr() + cur_write_pos, data, copy1_len); - memcpy(usable_region_ptr(), - reinterpret_cast<const uint8_t*>(data) + copy1_len, - len - copy1_len); - } - - // Atomically update the write position. - // We also verify that the write position did not advance, it SHOULD NEVER - // advance since we were holding the write lock. - if (write_pos().exchange((cur_write_pos + len) % usable_len()) != - cur_write_pos) { - UnlockForWriting(); - return Error::kControlCorruption; - } - - UnlockForWriting(); - - return Error::kSuccess; - } - - Error TryReadLocked(void* data, uint32_t len, uint32_t* bytes_read) { - uint32_t cur_read_pos = read_pos().load(); - uint32_t cur_write_pos = write_pos().load(); - - if (!ValidateReadWritePositions(cur_read_pos, cur_write_pos)) { - return Error::kControlCorruption; - } - - // The most we can read is the smaller of what's in use in the shared memory - // usable area and the buffer size we've been passed. - uint32_t bytes_available_to_read = - NumBytesInUse(cur_read_pos, cur_write_pos); - bytes_available_to_read = std::min(bytes_available_to_read, len); - if (bytes_available_to_read == 0) { - *bytes_read = 0; - return Error::kSuccess; - } - - // We have two cases when reading, the first is the read position is behind - // the write position, in that case we can simply read all data between the - // read and write position (up to our buffer size). The second case is when - // the write position is behind the read position. In this situation we must - // read from the read position to the end of the available area, and - // continue reading from the 0 position up to the write position or the - // maximum buffer size (bytes_available_to_read). - if (cur_read_pos < cur_write_pos) { - memcpy(data, usable_region_ptr() + cur_read_pos, bytes_available_to_read); - } else { - // We first start by reading to the end of the the usable area, if we - // cannot read all the way (because our buffer is too small, we're done). - uint32_t bytes_from_read_to_end = usable_len() - cur_read_pos; - bytes_from_read_to_end = - std::min(bytes_from_read_to_end, bytes_available_to_read); - memcpy(data, usable_region_ptr() + cur_read_pos, bytes_from_read_to_end); - - if (bytes_from_read_to_end < bytes_available_to_read) { - memcpy(reinterpret_cast<uint8_t*>(data) + bytes_from_read_to_end, - usable_region_ptr(), - bytes_available_to_read - bytes_from_read_to_end); - } - } - - // Atomically update the read position. - // We also verify that the read position did not advance, it SHOULD NEVER - // advance since we were holding the read lock. - uint32_t new_read_pos = - (cur_read_pos + bytes_available_to_read) % usable_len(); - if (read_pos().exchange(new_read_pos) != cur_read_pos) { - *bytes_read = 0; - return Error::kControlCorruption; - } - - *bytes_read = bytes_available_to_read; - return Error::kSuccess; - } - - bool TryLockForReading() { - // We return true if we set the flag (meaning it was false). - return !read_flag().test_and_set(std::memory_order_acquire); - } - - void UnlockForReading() { read_flag().clear(std::memory_order_release); } - - private: - struct ControlStructure { - std::atomic_flag write_flag{false}; - std::atomic_uint32_t write_pos{0}; - - std::atomic_flag read_flag{false}; - std::atomic_uint32_t read_pos{0}; - - // If we're using a notification mechanism that relies on futex, make the - // space available for one, if not these 32bits are unused. The kernel - // requires they be 32bit aligned. - alignas(4) volatile uint32_t futex = 0; - }; - - // This function will only validate that the values provided for write and - // read positions are valid based on usable size of the shared memory region. - // This should ALWAYS be called before attempting a write or read using - // atomically loaded values from the control structure. - bool ValidateReadWritePositions(uint32_t read_pos, uint32_t write_pos) { - // The only valid values for read and write positions are [0 - usable_len - // - 1]. - if (write_pos >= usable_len()) { - LOG(ERROR) << "Write position of shared buffer is currently beyond the " - "usable length"; - return false; - } - - if (read_pos >= usable_len()) { - LOG(ERROR) << "Read position of shared buffer is currently beyond the " - "usable length"; - return false; - } - - return true; - } - - // NumBytesInUse will calculate how many bytes in the shared buffer are - // currently in use. - uint32_t NumBytesInUse(uint32_t read_pos, uint32_t write_pos) { - uint32_t bytes_in_use = 0; - if (read_pos <= write_pos) { - bytes_in_use = write_pos - read_pos; - } else { - bytes_in_use = write_pos + (usable_len() - read_pos); - } - - return bytes_in_use; - } - - bool TryLockForWriting() { - // We return true if we set the flag (meaning it was false). - return !write_flag().test_and_set(std::memory_order_acquire); - } - - void UnlockForWriting() { write_flag().clear(std::memory_order_release); } - - // This is the space we need to reserve in this shared buffer for our control - // structure at the start. - constexpr static size_t kReservedSpace = - RoundUpToWordBoundary(sizeof(ControlStructure)); - - std::atomic_flag& write_flag() { - DCHECK(is_valid()); - return reinterpret_cast<ControlStructure*>(base_ptr_)->write_flag; - } - - std::atomic_flag& read_flag() { - DCHECK(is_valid()); - return reinterpret_cast<ControlStructure*>(base_ptr_)->read_flag; - } - - std::atomic_uint32_t& read_pos() { - DCHECK(is_valid()); - return reinterpret_cast<ControlStructure*>(base_ptr_)->read_pos; - } - - std::atomic_uint32_t& write_pos() { - DCHECK(is_valid()); - return reinterpret_cast<ControlStructure*>(base_ptr_)->write_pos; - } - - SharedBuffer(uint8_t* ptr, size_t len) : base_ptr_(ptr), len_(len) {} - - uint8_t* base_ptr_ = nullptr; - size_t len_ = 0; - - DISALLOW_COPY_AND_ASSIGN(SharedBuffer); -}; - -ChannelLinux::ChannelLinux( - Delegate* delegate, - ConnectionParams connection_params, - HandlePolicy handle_policy, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) - : ChannelPosix(delegate, - std::move(connection_params), - handle_policy, - io_task_runner), - num_pages_(g_shared_mem_pages.load()) {} - -ChannelLinux::~ChannelLinux() = default; - -void ChannelLinux::Write(MessagePtr message) { - if (!shared_mem_writer_ || message->has_handles() || reject_writes_) { - // Let the ChannelPosix deal with this. - return ChannelPosix::Write(std::move(message)); - } - - // Can we use the fast shared memory buffer? - SharedBuffer::Error write_result = - write_buffer_->TryWrite(message->data(), message->data_num_bytes()); - if (write_result == SharedBuffer::Error::kGeneralError) { - // We can handle this with the posix channel. - return ChannelPosix::Write(std::move(message)); - } else if (write_result == SharedBuffer::Error::kControlCorruption) { - // We will no longer be issuing writes via shared memory, and we will - // dispatch a write error. - reject_writes_ = true; - - // Theoretically we could fall back to only using PosixChannel::Write - // but if this situation happens it's likely something else is going - // horribly wrong. - io_task_runner_->PostTask( - FROM_HERE, base::BindOnce(&ChannelLinux::OnWriteError, this, - Channel::Error::kReceivedMalformedData)); - return; - } - - // The write with shared memory was successful. - UMA_HISTOGRAM_COUNTS_100000("Mojo.Channel.Linux.SharedMemWriteBytes", - message->data_num_bytes()); - write_notifier_->Notify(); -} - -void ChannelLinux::OfferSharedMemUpgrade() { - if (!offered_.test_and_set() && UpgradesEnabled()) { - // Before we offer we need to make sure we can send handles, if we can't - // then no point in trying. - if (handle_policy() == HandlePolicy::kAcceptHandles) { - OfferSharedMemUpgradeInternal(); - } - } -} - -bool ChannelLinux::OnControlMessage(Message::MessageType message_type, - const void* payload, - size_t payload_size, - std::vector<PlatformHandle> handles) { - switch (message_type) { - case Message::MessageType::UPGRADE_OFFER: { - const UpgradeOfferMessage* msg = - reinterpret_cast<const UpgradeOfferMessage*>(payload); - if (payload_size < sizeof(UpgradeOfferMessage)) { - LOG(ERROR) << "Received a malformed UPGRADE_OFFER message"; - return true; - } - - if (msg->version != UpgradeOfferMessage::kSupportedVersion) { - LOG(ERROR) << "Reject shared mem upgrade unexpected version: " - << msg->version; - RejectUpgradeOffer(); - return true; - } - - if (handles.size() != 2) { - LOG(ERROR) << "Received an UPGRADE_OFFER without two FDs"; - RejectUpgradeOffer(); - return true; - } - - if (read_buffer_ || read_notifier_) { - LOG(ERROR) << "Received an UPGRADE_OFFER on already upgraded channel"; - return true; - } - - base::ScopedFD memfd(handles[0].TakeFD()); - if (memfd.is_valid() && !ValidateFDIsProperlySealedMemFD(memfd)) { - PLOG(ERROR) << "Passed FD was not properly sealed"; - DLOG(FATAL) << "MemFD was NOT properly sealed"; - memfd.reset(); - } - - if (!memfd.is_valid()) { - RejectUpgradeOffer(); - return true; - } - - if (msg->num_pages <= 0 || msg->num_pages > 128) { - LOG(ERROR) << "SharedMemory upgrade offer was received with invalid " - "number of pages: " - << msg->num_pages; - RejectUpgradeOffer(); - } - - std::unique_ptr<DataAvailableNotifier> read_notifier; - if (msg->version == UpgradeOfferMessage::kEventFdNotifier) { - read_notifier = EventFDNotifier::CreateReadNotifier( - handles[1].TakeFD(), - base::BindRepeating(&ChannelLinux::SharedMemReadReady, this), - io_task_runner_); - } - - if (!read_notifier) { - RejectUpgradeOffer(); - return true; - } - - read_notifier_ = std::move(read_notifier); - - std::unique_ptr<SharedBuffer> read_sb = SharedBuffer::Create( - std::move(memfd), msg->num_pages * base::GetPageSize()); - if (!read_sb || !read_sb->is_valid()) { - RejectUpgradeOffer(); - return true; - } - - read_buffer_ = std::move(read_sb); - - read_buf_.resize(read_buffer_->usable_len()); - AcceptUpgradeOffer(); - - // And if we haven't offered ourselves just go ahead and do it now. - OfferSharedMemUpgrade(); - return true; - } - - case Message::MessageType::UPGRADE_ACCEPT: { - if (!write_buffer_ || !write_notifier_ || !write_notifier_->is_valid()) { - LOG(ERROR) << "Received unexpected UPGRADE_ACCEPT"; - - // Clean up anything that may have been set. - shared_mem_writer_ = false; - write_buffer_.reset(); - write_notifier_.reset(); - return true; - } - - shared_mem_writer_ = true; - return true; - } - - case Message::MessageType::UPGRADE_REJECT: { - // We can free our resources. - shared_mem_writer_ = false; - write_buffer_.reset(); - write_notifier_.reset(); - - return true; - } - default: - break; - } - - return ChannelPosix::OnControlMessage(message_type, payload, payload_size, - std::move(handles)); -} - -void ChannelLinux::SharedMemReadReady() { - CHECK(read_buffer_); - if (read_buffer_->TryLockForReading()) { - read_notifier_->Clear(); - do { - uint32_t bytes_read = 0; - SharedBuffer::Error read_res = read_buffer_->TryReadLocked( - read_buf_.data(), read_buf_.size(), &bytes_read); - if (read_res == SharedBuffer::Error::kControlCorruption) { - // This is an error we cannot recover from. - OnError(Error::kReceivedMalformedData); - read_buffer_->UnlockForReading(); - break; - } - - if (bytes_read == 0) { - break; - } - - UMA_HISTOGRAM_COUNTS_100000("Mojo.Channel.Linux.SharedMemReadBytes", - bytes_read); - - // Now dispatch the message, we KNOW it's at least one full message - // because we checked the message size before putting it into the - // shared buffer, this mechanism can never write a partial message. - off_t data_offset = 0; - while (bytes_read - data_offset > 0) { - size_t read_size_hint; - DispatchResult result = TryDispatchMessage( - base::make_span( - reinterpret_cast<char*>(read_buf_.data() + data_offset), - bytes_read - data_offset), - &read_size_hint); - - // We cannot have a message parse failure, we KNOW that we wrote a - // full message if we get one something has gone horribly wrong. - if (result != DispatchResult::kOK) { - LOG(ERROR) << "Recevied a bad message via shared memory"; - OnError(Error::kReceivedMalformedData); - break; - } - - // The next message will start after read_size_hint bytes the writer - // guarantees that we wrote a full message and we've guaranteed that the - // message was dispatched correctly so we know where the next message - // starts. - data_offset += read_size_hint; - } - } while (true); - read_buffer_->UnlockForReading(); - } -} - -void ChannelLinux::OnWriteError(Error error) { - reject_writes_ = true; - ChannelPosix::OnWriteError(error); -} - -void ChannelLinux::ShutDownOnIOThread() { - reject_writes_ = true; - read_notifier_.reset(); - write_notifier_.reset(); - - ChannelPosix::ShutDownOnIOThread(); -} - -void ChannelLinux::StartOnIOThread() { - ChannelPosix::StartOnIOThread(); -} - -void ChannelLinux::OfferSharedMemUpgradeInternal() { - if (reject_writes_) { - return; - } - - if (write_buffer_ || write_notifier_) { - LOG(ERROR) << "Upgrade attempted on an already upgraded channel"; - return; - } - - const size_t kSize = num_pages_ * base::GetPageSize(); - base::ScopedFD memfd = CreateSealedMemFD(kSize); - if (!memfd.is_valid()) { - PLOG(ERROR) << "Unable to create memfd"; - return; - } - - bool properly_sealed = ValidateFDIsProperlySealedMemFD(memfd); - if (!properly_sealed) { - // We will not attempt an offer, something has gone wrong. - LOG(ERROR) << "FD was not properly sealed we cannot offer upgrade."; - return; - } - - std::unique_ptr<SharedBuffer> write_buffer = - SharedBuffer::Create(memfd, kSize); - if (!write_buffer || !write_buffer->is_valid()) { - PLOG(ERROR) << "Unable to map shared memory"; - return; - } - - write_buffer->Initialize(); - - std::unique_ptr<EventFDNotifier> write_notifier = - EventFDNotifier::CreateWriteNotifier(); - if (!write_notifier) { - PLOG(ERROR) << "Failed to create eventfd write notifier"; - return; - } - - std::vector<PlatformHandle> fds; - fds.emplace_back(std::move(memfd)); - fds.emplace_back(write_notifier->take_dup()); - - write_notifier_ = std::move(write_notifier); - write_buffer_ = std::move(write_buffer); - - UpgradeOfferMessage offer_msg; - offer_msg.num_pages = num_pages_; - MessagePtr msg(new Channel::Message(sizeof(UpgradeOfferMessage), - /*num handles=*/fds.size(), - Message::MessageType::UPGRADE_OFFER)); - msg->SetHandles(std::move(fds)); - memcpy(msg->mutable_payload(), &offer_msg, sizeof(offer_msg)); - - ChannelPosix::Write(std::move(msg)); -} - -// static -bool ChannelLinux::KernelSupportsUpgradeRequirements() { - static bool supported = []() -> bool { - // Do we have memfd_create support, we check by seeing if we get an -ENOSYS - // or an -EINVAL. We also support -EPERM because of seccomp rules this is - // another possible outcome. - int ret = syscall(__NR_memfd_create, "", ~0); - PCHECK(ret < 0 && (errno == EINVAL || errno == ENOSYS || errno == EPERM)); - bool memfd_supported = (ret < 0 && errno == EINVAL); - return memfd_supported && EventFDNotifier::KernelSupported(); - }(); - return supported; -} - -// static -bool ChannelLinux::UpgradesEnabled() { - if (!g_params_set.load()) - return g_use_shared_mem.load(); - - return base::FeatureList::IsEnabled(kMojoLinuxChannelSharedMem); -} - -// static -void ChannelLinux::SetSharedMemParameters(bool enabled, uint32_t num_pages) { - g_params_set.store(true); - g_use_shared_mem.store(enabled); - g_shared_mem_pages.store(num_pages); -} - -} // namespace core -} // namespace mojo
diff --git a/mojo/core/channel_linux.h b/mojo/core/channel_linux.h deleted file mode 100644 index 9793804a..0000000 --- a/mojo/core/channel_linux.h +++ /dev/null
@@ -1,95 +0,0 @@ -// 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 MOJO_CORE_CHANNEL_LINUX_H_ -#define MOJO_CORE_CHANNEL_LINUX_H_ - -#include <atomic> -#include <memory> - -#include "base/macros.h" -#include "build/build_config.h" -#include "mojo/core/channel_posix.h" - -namespace mojo { -namespace core { - -class DataAvailableNotifier; - -// ChannelLinux is a specialization of ChannelPosix which has support for shared -// memory via Mojo channel upgrades. By default on Linux, CrOS, and Android -// every channel will be of type ChannelLinux which can be upgraded at runtime -// to take advantage of shared memory when all required kernel features are -// present. -class MOJO_SYSTEM_IMPL_EXPORT ChannelLinux : public ChannelPosix { - public: - ChannelLinux(Delegate* delegate, - ConnectionParams connection_params, - HandlePolicy handle_policy, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner); - - ChannelLinux(const ChannelLinux&) = delete; - ChannelLinux& operator=(const ChannelLinux&) = delete; - - // KernelSupportsUpgradeRequirements will return true if the kernel supports - // the features necessary to use an upgrade channel. How the channel will be - // upgraded is an implementation detail and this just tells the caller that - // calling Channel::UpgradeChannel() will have some effect. - static bool KernelSupportsUpgradeRequirements(); - - // Will return true if at least one feature that is available via upgrade is - // enabled. - static bool UpgradesEnabled(); - - // SetSharedMemParams will control whether shared memory is used for this - // channel. - static void SetSharedMemParameters(bool enabled, uint32_t num_pages); - - // ChannelPosix impl: - void Write(MessagePtr message) override; - void OfferSharedMemUpgrade(); - bool OnControlMessage(Message::MessageType message_type, - const void* payload, - size_t payload_size, - std::vector<PlatformHandle> handles) override; - void OnWriteError(Error error) override; - - void StartOnIOThread() override; - void ShutDownOnIOThread() override; - - private: - ~ChannelLinux() override; - - class SharedBuffer; - - void OfferSharedMemUpgradeInternal(); - void SharedMemReadReady(); - - // We only offer once, we use an atomic flag to guarantee no races to offer. - std::atomic_flag offered_{false}; - - // This flag keeps track of whether or not we've established a shared memory - // channel with the remote. If false we always fall back to the PosixChannel - // (socket). - bool shared_mem_writer_ = false; - - std::unique_ptr<DataAvailableNotifier> write_notifier_; - std::unique_ptr<SharedBuffer> write_buffer_; - - std::unique_ptr<DataAvailableNotifier> read_notifier_; - std::unique_ptr<SharedBuffer> read_buffer_; - - uint32_t num_pages_ = 0; - - bool reject_writes_ = false; - - // This is a temporary buffer we use to remove messages from the shared buffer - // for validation and dispatching. - std::vector<uint8_t> read_buf_; -}; - -} // namespace core -} // namespace mojo - -#endif
diff --git a/mojo/core/channel_posix.cc b/mojo/core/channel_posix.cc index 5dd7359..2335e8d2 100644 --- a/mojo/core/channel_posix.cc +++ b/mojo/core/channel_posix.cc
@@ -31,20 +31,15 @@ #if !defined(OS_NACL) #include <limits.h> #include <sys/uio.h> - -#if (defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)) -#include "mojo/core/channel_linux.h" #endif -#endif // !defined(OS_NACL) - namespace mojo { namespace core { namespace { #if !defined(OS_NACL) std::atomic<bool> g_use_writev{false}; -#endif // !defined(OS_NACL) +#endif const size_t kMaxBatchReadCapacity = 256 * 1024; } // namespace @@ -261,11 +256,11 @@ ignore_result(server_.TakePlatformHandle()); } #if defined(OS_IOS) - fds_to_close_.clear(); + fds_to_close_.clear(); #endif - // May destroy the |this| if it was the last reference. - self_ = nullptr; + // May destroy the |this| if it was the last reference. + self_ = nullptr; } void ChannelPosix::WillDestroyCurrentMessageLoop() { @@ -278,67 +273,68 @@ if (server_.is_valid()) { CHECK_EQ(fd, server_.platform_handle().GetFD().get()); #if !defined(OS_NACL) - read_watcher_.reset(); - base::CurrentThread::Get()->RemoveDestructionObserver(this); + read_watcher_.reset(); + base::CurrentThread::Get()->RemoveDestructionObserver(this); - AcceptSocketConnection(server_.platform_handle().GetFD().get(), &socket_); - ignore_result(server_.TakePlatformHandle()); - if (!socket_.is_valid()) { - OnError(Error::kConnectionFailed); - return; - } - StartOnIOThread(); -#else - NOTREACHED(); -#endif - return; - } - CHECK_EQ(fd, socket_.get()); - - bool validation_error = false; - bool read_error = false; - size_t next_read_size = 0; - size_t buffer_capacity = 0; - size_t total_bytes_read = 0; - size_t bytes_read = 0; - do { - buffer_capacity = next_read_size; - char* buffer = GetReadBuffer(&buffer_capacity); - DCHECK_GT(buffer_capacity, 0u); - - std::vector<base::ScopedFD> incoming_fds; - ssize_t read_result = - SocketRecvmsg(socket_.get(), buffer, buffer_capacity, &incoming_fds); - for (auto& incoming_fd : incoming_fds) - incoming_fds_.emplace_back(std::move(incoming_fd)); - - if (read_result > 0) { - bytes_read = static_cast<size_t>(read_result); - total_bytes_read += bytes_read; - if (!OnReadComplete(bytes_read, &next_read_size)) { - read_error = true; - validation_error = true; - break; + AcceptSocketConnection(server_.platform_handle().GetFD().get(), &socket_); + ignore_result(server_.TakePlatformHandle()); + if (!socket_.is_valid()) { + OnError(Error::kConnectionFailed); + return; } - } else if (read_result == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { - read_error = true; - break; - } else { - // We expect more data but there is none to read. The - // FileDescriptorWatcher will wake us up again once there is. - DCHECK(errno == EAGAIN || errno == EWOULDBLOCK); + StartOnIOThread(); +#else + NOTREACHED(); +#endif return; - } - } while (bytes_read == buffer_capacity && - total_bytes_read < kMaxBatchReadCapacity && next_read_size > 0); - if (read_error) { - // Stop receiving read notifications. - read_watcher_.reset(); - if (validation_error) - OnError(Error::kReceivedMalformedData); - else - OnError(Error::kDisconnected); } + CHECK_EQ(fd, socket_.get()); + + bool validation_error = false; + bool read_error = false; + size_t next_read_size = 0; + size_t buffer_capacity = 0; + size_t total_bytes_read = 0; + size_t bytes_read = 0; + do { + buffer_capacity = next_read_size; + char* buffer = GetReadBuffer(&buffer_capacity); + DCHECK_GT(buffer_capacity, 0u); + + std::vector<base::ScopedFD> incoming_fds; + ssize_t read_result = + SocketRecvmsg(socket_.get(), buffer, buffer_capacity, &incoming_fds); + for (auto& incoming_fd : incoming_fds) + incoming_fds_.emplace_back(std::move(incoming_fd)); + + if (read_result > 0) { + bytes_read = static_cast<size_t>(read_result); + total_bytes_read += bytes_read; + if (!OnReadComplete(bytes_read, &next_read_size)) { + read_error = true; + validation_error = true; + break; + } + } else if (read_result == 0 || + (errno != EAGAIN && errno != EWOULDBLOCK)) { + read_error = true; + break; + } else { + // We expect more data but there is none to read. The + // FileDescriptorWatcher will wake us up again once there is. + DCHECK(errno == EAGAIN || errno == EWOULDBLOCK); + return; + } + } while (bytes_read == buffer_capacity && + total_bytes_read < kMaxBatchReadCapacity && next_read_size > 0); + if (read_error) { + // Stop receiving read notifications. + read_watcher_.reset(); + if (validation_error) + OnError(Error::kReceivedMalformedData); + else + OnError(Error::kDisconnected); + } } void ChannelPosix::OnFileCanWriteWithoutBlocking(int fd) { @@ -381,29 +377,29 @@ result = SendmsgWithHandles(socket_.get(), &iov, 1, fds); if (result >= 0) { #if defined(OS_IOS) - // There is a bug in XNU which makes it dangerous to close - // a file descriptor while it is in transit. So instead we - // store the file descriptor in a set and send a message to - // the recipient, which is queued AFTER the message that - // sent the FD. The recipient will reply to the message, - // letting us know that it is now safe to close the file - // descriptor. For more information, see: - // http://crbug.com/298276 - MessagePtr fds_message(new Channel::Message( - sizeof(int) * fds.size(), 0, Message::MessageType::HANDLES_SENT)); - int* fd_data = reinterpret_cast<int*>(fds_message->mutable_payload()); - for (size_t i = 0; i < fds.size(); ++i) - fd_data[i] = fds[i].get(); - outgoing_messages_.emplace_back(std::move(fds_message), 0); - { - base::AutoLock l(fds_to_close_lock_); - for (auto& fd : fds) - fds_to_close_.emplace_back(std::move(fd)); - } + // There is a bug in XNU which makes it dangerous to close + // a file descriptor while it is in transit. So instead we + // store the file descriptor in a set and send a message to + // the recipient, which is queued AFTER the message that + // sent the FD. The recipient will reply to the message, + // letting us know that it is now safe to close the file + // descriptor. For more information, see: + // http://crbug.com/298276 + MessagePtr fds_message(new Channel::Message( + sizeof(int) * fds.size(), 0, Message::MessageType::HANDLES_SENT)); + int* fd_data = reinterpret_cast<int*>(fds_message->mutable_payload()); + for (size_t i = 0; i < fds.size(); ++i) + fd_data[i] = fds[i].get(); + outgoing_messages_.emplace_back(std::move(fds_message), 0); + { + base::AutoLock l(fds_to_close_lock_); + for (auto& fd : fds) + fds_to_close_.emplace_back(std::move(fd)); + } #endif // defined(OS_IOS) - handles_written += num_handles_to_send; - DCHECK_LE(handles_written, num_handles); - message_view.set_num_handles_sent(handles_written); + handles_written += num_handles_to_send; + DCHECK_LE(handles_written, num_handles); + message_view.set_num_handles_sent(handles_written); } else { // Message transmission failed, so pull the FDs back into |handles| // so they can be held by the Message again. @@ -417,86 +413,76 @@ message_view.data_num_bytes()); } - if (result < 0) { - if (errno != EAGAIN && - errno != EWOULDBLOCK + if (result < 0) { + if (errno != EAGAIN && + errno != EWOULDBLOCK #if defined(OS_IOS) - // On iOS if sendmsg() is trying to send fds between processes and - // there isn't enough room in the output buffer to send the fd - // structure over atomically then EMSGSIZE is returned. - // - // EMSGSIZE presents a problem since the system APIs can only call - // us when there's room in the socket buffer and not when there is - // "enough" room. - // - // The current behavior is to return to the event loop when EMSGSIZE - // is received and hopefully service another FD. This is however - // still technically a busy wait since the event loop will call us - // right back until the receiver has read enough data to allow - // passing the FD over atomically. - && errno != EMSGSIZE + // On iOS if sendmsg() is trying to send fds between processes and + // there isn't enough room in the output buffer to send the fd + // structure over atomically then EMSGSIZE is returned. + // + // EMSGSIZE presents a problem since the system APIs can only call + // us when there's room in the socket buffer and not when there is + // "enough" room. + // + // The current behavior is to return to the event loop when EMSGSIZE + // is received and hopefull service another FD. This is however + // still technically a busy wait since the event loop will call us + // right back until the receiver has read enough data to allow + // passing the FD over atomically. + && errno != EMSGSIZE #endif - ) { - return false; + ) { + return false; + } + message_view.SetHandles(std::move(handles)); + outgoing_messages_.emplace_front(std::move(message_view)); + WaitForWriteOnIOThreadNoLock(); + return true; } - message_view.SetHandles(std::move(handles)); - outgoing_messages_.emplace_front(std::move(message_view)); - WaitForWriteOnIOThreadNoLock(); - return true; - } - bytes_written = static_cast<size_t>(result); + bytes_written = static_cast<size_t>(result); } while (handles_written < num_handles || bytes_written < message_view.data_num_bytes()); - return FlushOutgoingMessagesNoLock(); + return FlushOutgoingMessagesNoLock(); } bool ChannelPosix::FlushOutgoingMessagesNoLock() { #if !defined(OS_NACL) - if (g_use_writev) - return FlushOutgoingMessagesWritevNoLock(); + if (g_use_writev) + return FlushOutgoingMessagesWritevNoLock(); #endif - base::circular_deque<MessageView> messages; - std::swap(outgoing_messages_, messages); + base::circular_deque<MessageView> messages; + std::swap(outgoing_messages_, messages); - if (!messages.empty()) { - UMA_HISTOGRAM_COUNTS_1000("Mojo.Channel.WriteQueuePendingMessages", - messages.size()); - } - - while (!messages.empty()) { - if (!WriteNoLock(std::move(messages.front()))) - return false; - - messages.pop_front(); - if (!outgoing_messages_.empty()) { - // The message was requeued by WriteNoLock(), so we have to wait for - // pipe to become writable again. Repopulate the message queue and exit. - // If sending the message triggered any control messages, they may be - // in |outgoing_messages_| in addition to or instead of the message - // being sent. - std::swap(messages, outgoing_messages_); - while (!messages.empty()) { - outgoing_messages_.push_front(std::move(messages.back())); - messages.pop_back(); - } - return true; + if (!messages.empty()) { + UMA_HISTOGRAM_COUNTS_1000("Mojo.Channel.WriteQueuePendingMessages", + messages.size()); } - } - return true; -} + while (!messages.empty()) { + if (!WriteNoLock(std::move(messages.front()))) + return false; -void ChannelPosix::RejectUpgradeOffer() { - Write(std::make_unique<Channel::Message>( - 0, 0, Message::MessageType::UPGRADE_REJECT)); -} + messages.pop_front(); + if (!outgoing_messages_.empty()) { + // The message was requeued by WriteNoLock(), so we have to wait for + // pipe to become writable again. Repopulate the message queue and exit. + // If sending the message triggered any control messages, they may be + // in |outgoing_messages_| in addition to or instead of the message + // being sent. + std::swap(messages, outgoing_messages_); + while (!messages.empty()) { + outgoing_messages_.push_front(std::move(messages.back())); + messages.pop_back(); + } + return true; + } + } -void ChannelPosix::AcceptUpgradeOffer() { - Write(std::make_unique<Channel::Message>( - 0, 0, Message::MessageType::UPGRADE_ACCEPT)); + return true; } void ChannelPosix::OnWriteError(Error error) { @@ -621,19 +607,12 @@ } #endif // !defined(OS_NACL) +#if defined(OS_IOS) bool ChannelPosix::OnControlMessage(Message::MessageType message_type, const void* payload, size_t payload_size, std::vector<PlatformHandle> handles) { switch (message_type) { - case Message::MessageType::UPGRADE_OFFER: { - // ChannelPosix itself does not support upgrades, if the message was - // delivered here it could have been when this channel was created we - // didn't support upgrades but another process does. - RejectUpgradeOffer(); - return true; - } -#if defined(OS_IOS) case Message::MessageType::HANDLES_SENT: { if (payload_size == 0) break; @@ -654,7 +633,7 @@ break; return true; } -#endif + default: break; } @@ -662,7 +641,6 @@ return false; } -#if defined(OS_IOS) // Closes handles referenced by |fds|. Returns false if |num_fds| is 0, or if // |fds| does not match a sequence of handles in |fds_to_close_|. bool ChannelPosix::CloseHandles(const int* fds, size_t num_fds) { @@ -697,12 +675,12 @@ } #endif // defined(OS_IOS) -#if !defined(OS_NACL) // static +#if !defined(OS_NACL) void Channel::set_posix_use_writev(bool use_writev) { g_use_writev = use_writev; } -#endif // !defined(OS_NACL) +#endif // static scoped_refptr<Channel> Channel::Create( @@ -710,33 +688,9 @@ ConnectionParams connection_params, HandlePolicy handle_policy, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) { -#if !defined(OS_NACL) -#if (defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)) - return new ChannelLinux(delegate, std::move(connection_params), handle_policy, - io_task_runner); -#endif -#endif - return new ChannelPosix(delegate, std::move(connection_params), handle_policy, io_task_runner); } -#if !defined(OS_NACL) -#if (defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)) -// static -bool Channel::SupportsChannelUpgrade() { - return ChannelLinux::KernelSupportsUpgradeRequirements() && - ChannelLinux::UpgradesEnabled(); -} - -void Channel::OfferChannelUpgrade() { - if (!SupportsChannelUpgrade()) { - return; - } - static_cast<ChannelLinux*>(this)->OfferSharedMemUpgrade(); -} -#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) -#endif // !defined(OS_NACL) - } // namespace core } // namespace mojo
diff --git a/mojo/core/channel_posix.h b/mojo/core/channel_posix.h index e64e64c..33a33aa 100644 --- a/mojo/core/channel_posix.h +++ b/mojo/core/channel_posix.h
@@ -43,28 +43,14 @@ size_t extra_header_size, std::vector<PlatformHandle>* handles, bool* deferred) override; - bool OnControlMessage(Message::MessageType message_type, - const void* payload, - size_t payload_size, - std::vector<PlatformHandle> handles) override; - - protected: - ~ChannelPosix() override; - virtual void StartOnIOThread(); - virtual void ShutDownOnIOThread(); - virtual void OnWriteError(Error error); - - void RejectUpgradeOffer(); - void AcceptUpgradeOffer(); - - // Keeps the Channel alive at least until explicit shutdown on the IO thread. - scoped_refptr<Channel> self_; - - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; private: + ~ChannelPosix() override; + + void StartOnIOThread(); void WaitForWriteOnIOThread(); void WaitForWriteOnIOThreadNoLock(); + void ShutDownOnIOThread(); // base::CurrentThread::DestructionObserver: void WillDestroyCurrentMessageLoop() override; @@ -92,9 +78,18 @@ #endif // !defined(OS_NACL) #if defined(OS_IOS) + bool OnControlMessage(Message::MessageType message_type, + const void* payload, + size_t payload_size, + std::vector<PlatformHandle> handles) override; bool CloseHandles(const int* fds, size_t num_fds); #endif // defined(OS_IOS) + void OnWriteError(Error error); + + // Keeps the Channel alive at least until explicit shutdown on the IO thread. + scoped_refptr<Channel> self_; + // We may be initialized with a server socket, in which case this will be // valid until it accepts an incoming connection. PlatformChannelServerEndpoint server_; @@ -103,6 +98,8 @@ // or accepted over |server_|. base::ScopedFD socket_; + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + // These watchers must only be accessed on the IO thread. std::unique_ptr<base::MessagePumpForIO::FdWatchController> read_watcher_; std::unique_ptr<base::MessagePumpForIO::FdWatchController> write_watcher_;
diff --git a/mojo/core/embedder/BUILD.gn b/mojo/core/embedder/BUILD.gn index 59495848..a2b8772 100644 --- a/mojo/core/embedder/BUILD.gn +++ b/mojo/core/embedder/BUILD.gn
@@ -21,19 +21,7 @@ public_deps = [ "//base" ] deps = [ - ":features", "//mojo/core:embedder_internal", "//mojo/public/c/system", ] } - -component("features") { - output_name = "mojo_core_embedder_features" - - defines = [ "IS_MOJO_CORE_EMBEDDER_FEATURES_IMPL" ] - - public = [ "features.h" ] - sources = [ "features.cc" ] - - public_deps = [ "//base" ] -}
diff --git a/mojo/core/embedder/embedder.cc b/mojo/core/embedder/embedder.cc index 30189c494..29b6d05 100644 --- a/mojo/core/embedder/embedder.cc +++ b/mojo/core/embedder/embedder.cc
@@ -15,40 +15,26 @@ #include "mojo/core/channel.h" #include "mojo/core/configuration.h" #include "mojo/core/core.h" -#include "mojo/core/embedder/features.h" #include "mojo/core/entrypoints.h" #include "mojo/core/node_controller.h" #include "mojo/public/c/system/thunks.h" -#if !defined(OS_NACL) -#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) -#include "mojo/core/channel_linux.h" -#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) -#endif // !defined(OS_NACL) - namespace mojo { namespace core { +namespace { +#if defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_MAC) +const base::Feature kMojoPosixUseWritev{"MojoPosixUseWritev", + base::FEATURE_DISABLED_BY_DEFAULT}; +#endif +} // namespace + // InitFeatures will be called as soon as the base::FeatureList is initialized. void InitFeatures() { #if defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_MAC) Channel::set_posix_use_writev( base::FeatureList::IsEnabled(kMojoPosixUseWritev)); - -#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) - bool shared_mem_enabled = - base::FeatureList::IsEnabled(kMojoLinuxChannelSharedMem); - int num_pages = kMojoLinuxChannelSharedMemPages.Get(); - if (num_pages < 0) { - num_pages = 4; - } else if (num_pages > 128) { - num_pages = 128; - } - - ChannelLinux::SetSharedMemParameters(shared_mem_enabled, - static_cast<unsigned int>(num_pages)); -#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) -#endif // defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_MAC) +#endif } void Init(const Configuration& configuration) {
diff --git a/mojo/core/embedder/features.cc b/mojo/core/embedder/features.cc deleted file mode 100644 index ed3f4f2..0000000 --- a/mojo/core/embedder/features.cc +++ /dev/null
@@ -1,26 +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. - -#include "mojo/core/embedder/features.h" - -namespace mojo { -namespace core { - -#if defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_MAC) -#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) -COMPONENT_EXPORT(MOJO_CORE_EMBEDDER_FEATURES) -const base::Feature kMojoLinuxChannelSharedMem{ - "MojoLinuxChannelSharedMem", base::FEATURE_DISABLED_BY_DEFAULT}; -COMPONENT_EXPORT(MOJO_CORE_EMBEDDER_FEATURES) -const base::FeatureParam<int> kMojoLinuxChannelSharedMemPages{ - &kMojoLinuxChannelSharedMem, "MojoLinuxChannelSharedMemPages", 4}; -#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) - -COMPONENT_EXPORT(MOJO_CORE_EMBEDDER_FEATURES) -const base::Feature kMojoPosixUseWritev{"MojoPosixUseWritev", - base::FEATURE_DISABLED_BY_DEFAULT}; -#endif // defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_MAC) - -} // namespace core -} // namespace mojo
diff --git a/mojo/core/embedder/features.h b/mojo/core/embedder/features.h deleted file mode 100644 index 5125335..0000000 --- a/mojo/core/embedder/features.h +++ /dev/null
@@ -1,27 +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 MOJO_CORE_EMBEDDER_FEATURES_H_ -#define MOJO_CORE_EMBEDDER_FEATURES_H_ - -#include "base/component_export.h" -#include "base/feature_list.h" -#include "build/build_config.h" - -namespace mojo { -namespace core { - -#if defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_MAC) -#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) -extern const base::Feature kMojoLinuxChannelSharedMem; -extern const base::FeatureParam<int> kMojoLinuxChannelSharedMemPages; -#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) - -extern const base::Feature kMojoPosixUseWritev; -#endif // defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_MAC) - -} // namespace core -} // namespace mojo - -#endif // MOJO_CORE_EMBEDDER_FEATURES_H_
diff --git a/mojo/core/node_channel.cc b/mojo/core/node_channel.cc index 33fb00ec..c48fb57 100644 --- a/mojo/core/node_channel.cc +++ b/mojo/core/node_channel.cc
@@ -59,22 +59,14 @@ ports::NodeName token; }; -struct alignas(8) AcceptInviteeDataV1 : AcceptInviteeDataV0 { - uint64_t capabilities = kNodeCapabilityNone; -}; - -using AcceptInviteeData = AcceptInviteeDataV1; +using AcceptInviteeData = AcceptInviteeDataV0; struct alignas(8) AcceptInvitationDataV0 { ports::NodeName token; ports::NodeName invitee_name; }; -struct alignas(8) AcceptInvitationDataV1 : AcceptInvitationDataV0 { - uint64_t capabilities = kNodeCapabilityNone; -}; - -using AcceptInvitationData = AcceptInvitationDataV1; +using AcceptInvitationData = AcceptInvitationDataV0; struct alignas(8) AcceptPeerDataV0 { ports::NodeName token; @@ -92,11 +84,7 @@ #endif }; -struct alignas(8) AddBrokerClientDataV1 : AddBrokerClientDataV0 { - uint64_t capabilities = kNodeCapabilityNone; -}; - -using AddBrokerClientData = AddBrokerClientDataV1; +using AddBrokerClientData = AddBrokerClientDataV0; #if !defined(OS_WIN) static_assert(sizeof(base::ProcessHandle) == sizeof(uint32_t), @@ -118,12 +106,7 @@ ports::NodeName broker_name; }; -struct alignas(8) AcceptBrokerClientDataV1 : AcceptBrokerClientDataV0 { - uint64_t capabilities = kNodeCapabilityNone; - uint64_t broker_capabilities = kNodeCapabilityNone; -}; - -using AcceptBrokerClientData = AcceptBrokerClientDataV1; +using AcceptBrokerClientData = AcceptBrokerClientDataV0; // This is followed by arbitrary payload data which is interpreted as a token // string for port location. @@ -142,11 +125,7 @@ ports::NodeName name; }; -struct alignas(8) IntroductionDataV1 : IntroductionDataV0 { - uint64_t capabilities = kNodeCapabilityNone; -}; - -using IntroductionData = IntroductionDataV1; +using IntroductionData = IntroductionDataV0; // This message is just a PlatformHandle. The data struct alignas(8) here has // only a padding field to ensure an aligned, non-zero-length payload. @@ -343,7 +322,6 @@ MessageType::ACCEPT_INVITEE, sizeof(AcceptInviteeData), 0, &data); data->inviter_name = inviter_name; data->token = token; - data->capabilities = local_capabilities_; WriteChannelMessage(std::move(message)); } @@ -354,7 +332,6 @@ MessageType::ACCEPT_INVITATION, sizeof(AcceptInvitationData), 0, &data); data->token = token; data->invitee_name = invitee_name; - data->capabilities = local_capabilities_; WriteChannelMessage(std::move(message)); } @@ -385,7 +362,6 @@ #if !defined(OS_WIN) data->process_handle = process_handle.Handle(); #endif - data->capabilities = local_capabilities_; WriteChannelMessage(std::move(message)); } @@ -404,8 +380,7 @@ } void NodeChannel::AcceptBrokerClient(const ports::NodeName& broker_name, - PlatformHandle broker_channel, - const uint64_t broker_capabilities) { + PlatformHandle broker_channel) { AcceptBrokerClientData* data; std::vector<PlatformHandle> handles; if (broker_channel.is_valid()) @@ -415,8 +390,6 @@ sizeof(AcceptBrokerClientData), handles.size(), &data); message->SetHandles(std::move(handles)); data->broker_name = broker_name; - data->broker_capabilities = broker_capabilities; - data->capabilities = local_capabilities_; WriteChannelMessage(std::move(message)); } @@ -440,8 +413,7 @@ } void NodeChannel::Introduce(const ports::NodeName& name, - PlatformHandle channel_handle, - uint64_t capabilities) { + PlatformHandle channel_handle) { IntroductionData* data; std::vector<PlatformHandle> handles; if (channel_handle.is_valid()) @@ -450,9 +422,6 @@ MessageType::INTRODUCE, sizeof(IntroductionData), handles.size(), &data); message->SetHandles(std::move(handles)); data->name = name; - // Note that these are not our capabilities, but the capabilities of the peer - // we're introducing. - data->capabilities = capabilities; WriteChannelMessage(std::move(message)); } @@ -547,7 +516,6 @@ std::move(io_task_runner))) #endif { - InitializeLocalCapabilities(); } NodeChannel::~NodeChannel() { @@ -581,10 +549,7 @@ switch (header->type) { case MessageType::ACCEPT_INVITEE: { AcceptInviteeData data; - if (GetMessagePayloadMinimumSized<AcceptInviteeData, AcceptInviteeDataV0>( - payload, payload_size, &data)) { - // Attach any capabilities that the other side advertised. - SetRemoteCapabilities(data.capabilities); + if (GetMessagePayload(payload, payload_size, &data)) { delegate_->OnAcceptInvitee(remote_node_name_, data.inviter_name, data.token); return; @@ -594,11 +559,7 @@ case MessageType::ACCEPT_INVITATION: { AcceptInvitationData data; - if (GetMessagePayloadMinimumSized<AcceptInvitationData, - AcceptInvitationDataV0>( - payload, payload_size, &data)) { - // Attach any capabilities that the other side advertised. - SetRemoteCapabilities(data.capabilities); + if (GetMessagePayload(payload, payload_size, &data)) { delegate_->OnAcceptInvitation(remote_node_name_, data.token, data.invitee_name); return; @@ -645,9 +606,7 @@ case MessageType::ACCEPT_BROKER_CLIENT: { AcceptBrokerClientData data; - if (GetMessagePayloadMinimumSized<AcceptBrokerClientData, - AcceptBrokerClientDataV0>( - payload, payload_size, &data)) { + if (GetMessagePayload(payload, payload_size, &data)) { PlatformHandle broker_channel; if (handles.size() > 1) { DLOG(ERROR) << "Dropping invalid AcceptBrokerClient message."; @@ -656,11 +615,8 @@ if (handles.size() == 1) broker_channel = std::move(handles[0]); - // Attach any capabilities that the other side advertised. - SetRemoteCapabilities(data.capabilities); delegate_->OnAcceptBrokerClient(remote_node_name_, data.broker_name, - std::move(broker_channel), - data.broker_capabilities); + std::move(broker_channel)); return; } break; @@ -703,8 +659,7 @@ case MessageType::INTRODUCE: { IntroductionData data; - if (GetMessagePayloadMinimumSized<IntroductionData, IntroductionDataV0>( - payload, payload_size, &data)) { + if (GetMessagePayload(payload, payload_size, &data)) { if (handles.size() > 1) { DLOG(ERROR) << "Dropping invalid introduction message."; break; @@ -713,11 +668,8 @@ if (handles.size() == 1) channel_handle = std::move(handles[0]); - // The node channel for this introduction will be created later, so we - // can only pass up the capabilities we received from the broker for - // that remote. delegate_->OnIntroduce(remote_node_name_, data.name, - std::move(channel_handle), data.capabilities); + std::move(channel_handle)); return; } break; @@ -851,42 +803,5 @@ channel_->Write(std::move(message)); } -void NodeChannel::OfferChannelUpgrade() { -#if !defined(OS_NACL) - base::AutoLock lock(channel_lock_); - channel_->OfferChannelUpgrade(); -#endif -} - -uint64_t NodeChannel::RemoteCapabilities() const { - return remote_capabilities_; -} - -bool NodeChannel::HasRemoteCapability(const uint64_t capability) const { - return (remote_capabilities_ & capability) == capability; -} - -void NodeChannel::SetRemoteCapabilities(const uint64_t capabilities) { - remote_capabilities_ |= capabilities; -} - -uint64_t NodeChannel::LocalCapabilities() const { - return local_capabilities_; -} - -bool NodeChannel::HasLocalCapability(const uint64_t capability) const { - return (local_capabilities_ & capability) == capability; -} - -void NodeChannel::SetLocalCapabilities(const uint64_t capabilities) { - local_capabilities_ |= capabilities; -} - -void NodeChannel::InitializeLocalCapabilities() { - if (core::Channel::SupportsChannelUpgrade()) { - SetLocalCapabilities(kNodeCapabilitySupportsUpgrade); - } -} - } // namespace core } // namespace mojo
diff --git a/mojo/core/node_channel.h b/mojo/core/node_channel.h index b037054..74306a5 100644 --- a/mojo/core/node_channel.h +++ b/mojo/core/node_channel.h
@@ -26,9 +26,6 @@ namespace mojo { namespace core { -constexpr uint64_t kNodeCapabilityNone = 0; -constexpr uint64_t kNodeCapabilitySupportsUpgrade = 1; - // Wraps a Channel to send and receive Node control messages. class MOJO_SYSTEM_IMPL_EXPORT NodeChannel : public base::RefCountedDeleteOnSequence<NodeChannel>, @@ -51,8 +48,7 @@ PlatformHandle broker_channel) = 0; virtual void OnAcceptBrokerClient(const ports::NodeName& from_node, const ports::NodeName& broker_name, - PlatformHandle broker_channel, - const uint64_t broker_capabilities) = 0; + PlatformHandle broker_channel) = 0; virtual void OnEventMessage(const ports::NodeName& from_node, Channel::MessagePtr message) = 0; virtual void OnRequestPortMerge(const ports::NodeName& from_node, @@ -62,8 +58,7 @@ const ports::NodeName& name) = 0; virtual void OnIntroduce(const ports::NodeName& from_node, const ports::NodeName& name, - PlatformHandle channel_handle, - const uint64_t remote_capabilities) = 0; + PlatformHandle channel_handle) = 0; virtual void OnBroadcast(const ports::NodeName& from_node, Channel::MessagePtr message) = 0; #if defined(OS_WIN) @@ -135,26 +130,15 @@ void BrokerClientAdded(const ports::NodeName& client_name, PlatformHandle broker_channel); void AcceptBrokerClient(const ports::NodeName& broker_name, - PlatformHandle broker_channel, - const uint64_t broker_capabilities); + PlatformHandle broker_channel); void RequestPortMerge(const ports::PortName& connector_port_name, const std::string& token); void RequestIntroduction(const ports::NodeName& name); - void Introduce(const ports::NodeName& name, - PlatformHandle channel_handle, - uint64_t capabilities); + void Introduce(const ports::NodeName& name, PlatformHandle channel_handle); void SendChannelMessage(Channel::MessagePtr message); void Broadcast(Channel::MessagePtr message); void BindBrokerHost(PlatformHandle broker_host_handle); - uint64_t RemoteCapabilities() const; - bool HasRemoteCapability(const uint64_t capability) const; - void SetRemoteCapabilities(const uint64_t capability); - - uint64_t LocalCapabilities() const; - bool HasLocalCapability(const uint64_t capability) const; - void SetLocalCapabilities(const uint64_t capability); - #if defined(OS_WIN) // Relay the message to the specified node via this channel. This is used to // pass windows handles between two processes that do not have permission to @@ -170,8 +154,6 @@ Channel::MessagePtr message); #endif - void OfferChannelUpgrade(); - private: friend class base::RefCountedDeleteOnSequence<NodeChannel>; friend class base::DeleteHelper<NodeChannel>; @@ -199,10 +181,6 @@ void WriteChannelMessage(Channel::MessagePtr message); - // This method is responsible for setting up the default set of capabilities - // for this channel. - void InitializeLocalCapabilities(); - Delegate* const delegate_; const ProcessErrorCallback process_error_callback_; @@ -212,9 +190,6 @@ // Must only be accessed from the owning task runner's thread. ports::NodeName remote_node_name_; - uint64_t remote_capabilities_ = kNodeCapabilityNone; - uint64_t local_capabilities_ = kNodeCapabilityNone; - base::Lock remote_process_handle_lock_; base::Process remote_process_handle_;
diff --git a/mojo/core/node_controller.cc b/mojo/core/node_controller.cc index 014dfd9a..86b397df 100644 --- a/mojo/core/node_controller.cc +++ b/mojo/core/node_controller.cc
@@ -867,8 +867,7 @@ if (!inviter) { // Yes, we're the broker. We can initialize the client directly. - channel->AcceptBrokerClient(name_, PlatformHandle(), - channel->LocalCapabilities()); + channel->AcceptBrokerClient(name_, PlatformHandle()); } else { // We aren't the broker, so wait for a broker connection. base::AutoLock lock(broker_lock_); @@ -936,14 +935,12 @@ DVLOG(1) << "Client " << client_name << " accepted by broker " << from_node; - client->AcceptBrokerClient(from_node, std::move(broker_channel), - GetBrokerChannel()->RemoteCapabilities()); + client->AcceptBrokerClient(from_node, std::move(broker_channel)); } void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node, const ports::NodeName& broker_name, - PlatformHandle broker_channel, - const uint64_t broker_capabilities) { + PlatformHandle broker_channel) { DCHECK(!GetConfiguration().is_broker_process); // This node should already have an inviter in bootstrap mode. @@ -982,7 +979,6 @@ ConnectionParams(PlatformChannelEndpoint(std::move(broker_channel))), Channel::HandlePolicy::kAcceptHandles, io_task_runner_, ProcessErrorCallback()); - broker->SetRemoteCapabilities(broker_capabilities); AddPeer(broker_name, broker, true /* start_channel */); } @@ -1018,10 +1014,6 @@ } } #endif - if (inviter->HasLocalCapability(kNodeCapabilitySupportsUpgrade) && - inviter->HasRemoteCapability(kNodeCapabilitySupportsUpgrade)) { - inviter->OfferChannelUpgrade(); - } DVLOG(1) << "Client " << name_ << " accepted by broker " << broker_name; } @@ -1100,22 +1092,19 @@ scoped_refptr<NodeChannel> new_friend = GetPeerChannel(name); if (!new_friend) { // We don't know who they're talking about! - requestor->Introduce(name, PlatformHandle(), kNodeCapabilityNone); + requestor->Introduce(name, PlatformHandle()); } else { PlatformChannel new_channel; requestor->Introduce(name, - new_channel.TakeLocalEndpoint().TakePlatformHandle(), - new_friend->RemoteCapabilities()); - new_friend->Introduce(from_node, - new_channel.TakeRemoteEndpoint().TakePlatformHandle(), - requestor->RemoteCapabilities()); + new_channel.TakeLocalEndpoint().TakePlatformHandle()); + new_friend->Introduce( + from_node, new_channel.TakeRemoteEndpoint().TakePlatformHandle()); } } void NodeController::OnIntroduce(const ports::NodeName& from_node, const ports::NodeName& name, - PlatformHandle channel_handle, - const uint64_t remote_capabilities) { + PlatformHandle channel_handle) { DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); if (!channel_handle.is_valid()) { @@ -1142,13 +1131,6 @@ DVLOG(1) << "Adding new peer " << name << " via broker introduction."; AddPeer(name, channel, true /* start_channel */); - - channel->SetRemoteCapabilities(remote_capabilities); - - if (channel->HasLocalCapability(kNodeCapabilitySupportsUpgrade) && - channel->HasRemoteCapability(kNodeCapabilitySupportsUpgrade)) { - channel->OfferChannelUpgrade(); - } } void NodeController::OnBroadcast(const ports::NodeName& from_node, @@ -1357,13 +1339,11 @@ NodeController::IsolatedConnection::~IsolatedConnection() = default; -NodeController::IsolatedConnection& -NodeController::IsolatedConnection::operator=(const IsolatedConnection& other) = - default; +NodeController::IsolatedConnection& NodeController::IsolatedConnection:: +operator=(const IsolatedConnection& other) = default; -NodeController::IsolatedConnection& -NodeController::IsolatedConnection::operator=(IsolatedConnection&& other) = - default; +NodeController::IsolatedConnection& NodeController::IsolatedConnection:: +operator=(IsolatedConnection&& other) = default; } // namespace core } // namespace mojo
diff --git a/mojo/core/node_controller.h b/mojo/core/node_controller.h index affbeda..254efe4 100644 --- a/mojo/core/node_controller.h +++ b/mojo/core/node_controller.h
@@ -206,8 +206,7 @@ PlatformHandle broker_channel) override; void OnAcceptBrokerClient(const ports::NodeName& from_node, const ports::NodeName& broker_name, - PlatformHandle broker_channel, - const uint64_t broker_capabilities) override; + PlatformHandle broker_channel) override; void OnEventMessage(const ports::NodeName& from_node, Channel::MessagePtr message) override; void OnRequestPortMerge(const ports::NodeName& from_node, @@ -217,8 +216,7 @@ const ports::NodeName& name) override; void OnIntroduce(const ports::NodeName& from_node, const ports::NodeName& name, - PlatformHandle channel_handle, - const uint64_t remote_capailities) override; + PlatformHandle channel_handle) override; void OnBroadcast(const ports::NodeName& from_node, Channel::MessagePtr message) override; #if defined(OS_WIN)
diff --git a/mojo/core/test/mock_node_channel_delegate.h b/mojo/core/test/mock_node_channel_delegate.h index f58552f..06ca968 100644 --- a/mojo/core/test/mock_node_channel_delegate.h +++ b/mojo/core/test/mock_node_channel_delegate.h
@@ -54,8 +54,7 @@ OnAcceptBrokerClient, (const NodeName& from_node, const NodeName& broker_name, - PlatformHandle broker_channel, - const uint64_t capabilities), + PlatformHandle broker_channel), (override)); MOCK_METHOD(void, OnEventMessage, @@ -75,8 +74,7 @@ OnIntroduce, (const NodeName& from_node, const NodeName& name, - PlatformHandle channel_handle, - const uint64_t remote_capabilites), + PlatformHandle channel_handle), (override)); MOCK_METHOD(void, OnBroadcast,
diff --git a/net/android/java/src/org/chromium/net/AndroidKeyStore.java b/net/android/java/src/org/chromium/net/AndroidKeyStore.java index c1b155d0..fde3ba1 100644 --- a/net/android/java/src/org/chromium/net/AndroidKeyStore.java +++ b/net/android/java/src/org/chromium/net/AndroidKeyStore.java
@@ -77,9 +77,6 @@ * @param algorithm The signature algorithm to use. * @param message The message to sign. * @return signature as a byte buffer. - * - * Note: NONEwithRSA is not implemented in Android < 4.2. See - * getOpenSSLHandleForPrivateKey() below for a work-around. */ @CalledByNative private static byte[] signWithPrivateKey( @@ -114,9 +111,6 @@ * @param algorithm The cipher to use. * @param input The input to encrypt. * @return ciphertext as a byte buffer. - * - * Note: NONEwithRSA is not implemented in Android < 4.2. See - * getOpenSSLHandleForPrivateKey() below for a work-around. */ @CalledByNative private static byte[] encryptWithPrivateKey(
diff --git a/net/base/network_change_notifier.cc b/net/base/network_change_notifier.cc index 7e7c64c..f98d4a7 100644 --- a/net/base/network_change_notifier.cc +++ b/net/base/network_change_notifier.cc
@@ -10,7 +10,7 @@ #include <utility> #include "base/memory/ref_counted.h" -#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" #include "base/no_destructor.h" #include "base/optional.h" #include "base/sequence_checker.h" @@ -156,8 +156,8 @@ return; } - base::UmaHistogramEnumeration("Net.NetworkChangeNotifier.NewConnectionType", - pending_connection_type_, CONNECTION_LAST); + UMA_HISTOGRAM_ENUMERATION("Net.NetworkChangeNotifier.NewConnectionType", + pending_connection_type_, CONNECTION_LAST + 1); have_announced_ = true; last_announced_connection_type_ = pending_connection_type_;
diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc index 5c70e1f..e00e3125 100644 --- a/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc +++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc
@@ -40,11 +40,9 @@ namespace { bool IsBaselinePolicyAllowed(int sysno) { - // clang-format off return SyscallSets::IsAllowedAddressSpaceAccess(sysno) || SyscallSets::IsAllowedBasicScheduler(sysno) || SyscallSets::IsAllowedEpoll(sysno) || - SyscallSets::IsEventFd(sysno) || SyscallSets::IsAllowedFileSystemAccessViaFd(sysno) || SyscallSets::IsAllowedFutex(sysno) || SyscallSets::IsAllowedGeneralIo(sysno) || @@ -61,7 +59,6 @@ SyscallSets::IsMipsPrivate(sysno) || #endif SyscallSets::IsAllowedOperationOnFd(sysno); - // clang-format on } // System calls that will trigger the crashing SIGSYS handler. @@ -71,6 +68,7 @@ SyscallSets::IsAdvancedTimer(sysno) || SyscallSets::IsAsyncIo(sysno) || SyscallSets::IsDebug(sysno) || + SyscallSets::IsEventFd(sysno) || SyscallSets::IsExtendedAttributes(sysno) || SyscallSets::IsFaNotify(sysno) || SyscallSets::IsFsControl(sysno) ||
diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc index c9d598c..35ab90e 100644 --- a/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc +++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc
@@ -151,6 +151,11 @@ break; } + // https://crbug.com/772441 and https://crbug.com/760020. + if (SyscallSets::IsEventFd(sysno)) { + return Allow(); + } + // Ptrace is allowed so the crash reporter can fork in a renderer // and then ptrace the parent. https://crbug.com/933418 if (sysno == __NR_ptrace) {
diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc index 68c29b5..01c046d 100644 --- a/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc +++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc
@@ -303,6 +303,7 @@ TEST_BASELINE_SIGSYS(__NR_timer_create) #if !defined(__aarch64__) +TEST_BASELINE_SIGSYS(__NR_eventfd) TEST_BASELINE_SIGSYS(__NR_inotify_init) TEST_BASELINE_SIGSYS(__NR_vserver) #endif
diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc index 8fa54f5..2a97d39 100644 --- a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc
@@ -215,7 +215,7 @@ // TODO(davidung), remove MAP_DENYWRITE with updated Tegra libraries. const uint64_t kAllowedMask = MAP_SHARED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_NORESERVE | MAP_FIXED | - MAP_DENYWRITE | MAP_LOCKED; + MAP_DENYWRITE; const Arg<int> flags(3); return If((flags & ~kAllowedMask) == 0, Allow()).Else(CrashSIGSYS()); } @@ -245,12 +245,9 @@ const uint64_t kAllowedMask = O_ACCMODE | O_APPEND | O_NONBLOCK | O_SYNC | kOLargeFileFlag | O_CLOEXEC | O_NOATIME; - const uint64_t kAllowedSeals = F_SEAL_SEAL | F_SEAL_GROW | F_SEAL_SHRINK; - // clang-format off return Switch(cmd) .CASES((F_GETFL, F_GETFD, - F_GET_SEALS, F_SETFD, F_SETLK, F_SETLKW, @@ -260,10 +257,7 @@ Allow()) .Case(F_SETFL, If((long_arg & ~kAllowedMask) == 0, Allow()).Else(CrashSIGSYS())) - .Case(F_ADD_SEALS, - If((long_arg & ~kAllowedSeals) == 0, Allow()).Else(CrashSIGSYS())) .Default(CrashSIGSYS()); - // clang-format on } #if defined(__i386__) || defined(__mips__)
diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc index f40d436..d9d18822 100644 --- a/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc
@@ -168,11 +168,9 @@ bool SyscallSets::IsAllowedFileSystemAccessViaFd(int sysno) { switch (sysno) { case __NR_fstat: - case __NR_ftruncate: #if defined(__i386__) || defined(__arm__) || \ (defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_32_BITS)) case __NR_fstat64: - case __NR_ftruncate64: #endif return true; // TODO(jln): these should be denied gracefully as well (moved below). @@ -213,9 +211,14 @@ case __NR_fallocate: case __NR_fchmod: case __NR_fchown: + case __NR_ftruncate: #if defined(__i386__) || defined(__arm__) case __NR_fchown32: #endif +#if defined(__i386__) || defined(__arm__) || \ + (defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_32_BITS)) + case __NR_ftruncate64: +#endif #if !defined(__aarch64__) case __NR_getdents: // EPERM not a valid errno. #endif
diff --git a/sandbox/policy/linux/bpf_base_policy_linux.cc b/sandbox/policy/linux/bpf_base_policy_linux.cc index 04bd9c84..90164ea 100644 --- a/sandbox/policy/linux/bpf_base_policy_linux.cc +++ b/sandbox/policy/linux/bpf_base_policy_linux.cc
@@ -31,11 +31,6 @@ ResultExpr BPFBasePolicy::EvaluateSyscall(int system_call_number) const { DCHECK(baseline_policy_); - // Used for shared memory Mojo channels on Linux. - if (system_call_number == __NR_memfd_create) { - return Allow(); - } - // set_robust_list(2) is part of the futex(2) infrastructure. // Chrome on Linux/Chrome OS will call set_robust_list(2) frequently. // The baseline policy will EPERM set_robust_list(2), but on systems with
diff --git a/sandbox/policy/linux/bpf_gpu_policy_linux.cc b/sandbox/policy/linux/bpf_gpu_policy_linux.cc index dd4c3116..829efee8b 100644 --- a/sandbox/policy/linux/bpf_gpu_policy_linux.cc +++ b/sandbox/policy/linux/bpf_gpu_policy_linux.cc
@@ -93,6 +93,8 @@ default: break; } + if (SyscallSets::IsEventFd(sysno)) + return Allow(); #if (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) && defined(USE_X11) if (SyscallSets::IsSystemVSharedMemory(sysno))
diff --git a/sandbox/policy/sandbox.cc b/sandbox/policy/sandbox.cc index 590ecc9c..95482e8 100644 --- a/sandbox/policy/sandbox.cc +++ b/sandbox/policy/sandbox.cc
@@ -58,16 +58,19 @@ SandboxInterfaceInfo* sandbox_info) { BrokerServices* broker_services = sandbox_info->broker_services; if (broker_services) { + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); if (!SandboxWin::InitBrokerServices(broker_services)) return false; - // IMPORTANT: This piece of code needs to run as early as possible in the - // process because it will initialize the sandbox broker, which requires the - // process to swap its window station. During this time all the UI will be - // broken. This has to run before threads and windows are created. - if (!IsUnsandboxedSandboxType(sandbox_type)) { - // Precreate the desktop and window station used by the renderers. + // Only pre-create alternate desktop if there will be sandboxed processes in + // the future. + if (!command_line.HasSwitch(switches::kNoSandbox)) { scoped_refptr<TargetPolicy> policy = broker_services->CreatePolicy(); + // IMPORTANT: This piece of code needs to run as early as possible in the + // process because it will initialize the sandbox broker, which requires + // the process to swap its window station. During this time all the UI + // will be broken. This has to run before threads and windows are created. ResultCode result = policy->CreateAlternateDesktop(true); CHECK(SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION != result); }
diff --git a/services/network/public/cpp/cors/cors_unittest.cc b/services/network/public/cpp/cors/cors_unittest.cc index 2113d78..58667f1 100644 --- a/services/network/public/cpp/cors/cors_unittest.cc +++ b/services/network/public/cpp/cors/cors_unittest.cc
@@ -6,6 +6,7 @@ #include <limits.h> +#include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" @@ -359,7 +360,12 @@ EXPECT_FALSE(IsCorsSafelistedHeader("AccepT", std::string(129, 'a'))); } -TEST_F(CorsTest, SafelistedAcceptLanguage) { +#if defined(OS_ANDROID) +#define MAYBE_SafelistedAcceptLanguage DISABLED_SafelistedAcceptLanguage +#else +#define MAYBE_SafelistedAcceptLanguage SafelistedAcceptLanguage +#endif +TEST_F(CorsTest, MAYBE_SafelistedAcceptLanguage) { EXPECT_TRUE(IsCorsSafelistedHeader("accept-language", "en,ja")); EXPECT_TRUE(IsCorsSafelistedHeader("aCcEPT-lAngUAge", "en,ja")); @@ -400,7 +406,12 @@ // https://crbug.com/924969 } -TEST_F(CorsTest, SafelistedContentLanguage) { +#if defined(OS_ANDROID) +#define MAYBE_SafelistedContentLanguage DISABLED_SafelistedContentLanguage +#else +#define MAYBE_SafelistedContentLanguage SafelistedContentLanguage +#endif +TEST_F(CorsTest, MAYBE_SafelistedContentLanguage) { EXPECT_TRUE(IsCorsSafelistedHeader("content-language", "en,ja")); EXPECT_TRUE(IsCorsSafelistedHeader("cONTent-LANguaGe", "en,ja"));
diff --git a/storage/browser/file_system/external_mount_points.cc b/storage/browser/file_system/external_mount_points.cc index 0c2e804..985a050e 100644 --- a/storage/browser/file_system/external_mount_points.cc +++ b/storage/browser/file_system/external_mount_points.cc
@@ -36,7 +36,7 @@ } bool IsOverlappingMountPathForbidden(FileSystemType type) { - return type != kFileSystemTypeNativeMedia && + return type != kFileSystemTypeLocalMedia && type != kFileSystemTypeDeviceMedia; } @@ -115,7 +115,7 @@ bool ExternalMountPoints::HandlesFileSystemMountType( FileSystemType type) const { return type == kFileSystemTypeExternal || - type == kFileSystemTypeNativeForPlatformApp; + type == kFileSystemTypeLocalForPlatformApp; } bool ExternalMountPoints::RevokeFileSystem(const std::string& mount_name) { @@ -264,7 +264,7 @@ return FileSystemURL(); base::FilePath virtual_path = url.path(); - if (url.type() == kFileSystemTypeNativeForPlatformApp) { + if (url.type() == kFileSystemTypeLocalForPlatformApp) { #if BUILDFLAG(IS_CHROMEOS_ASH) // On Chrome OS, find a mount point and virtual path for the external fs. if (!GetVirtualPath(url.path(), &virtual_path)) @@ -272,7 +272,7 @@ #else // On other OS, it is simply a native local path. return FileSystemURL(url.origin(), url.mount_type(), url.virtual_path(), - url.mount_filesystem_id(), kFileSystemTypeNativeLocal, + url.mount_filesystem_id(), kFileSystemTypeLocal, url.path(), url.filesystem_id(), url.mount_option()); #endif }
diff --git a/storage/browser/file_system/external_mount_points_unittest.cc b/storage/browser/file_system/external_mount_points_unittest.cc index 7911a42..e3df989 100644 --- a/storage/browser/file_system/external_mount_points_unittest.cc +++ b/storage/browser/file_system/external_mount_points_unittest.cc
@@ -111,9 +111,9 @@ // Test adding mount points. for (const auto& test : kTestCases) { EXPECT_EQ(test.success, - mount_points->RegisterFileSystem( - test.name, kFileSystemTypeNativeLocal, - FileSystemMountOption(), base::FilePath(test.path))) + mount_points->RegisterFileSystem(test.name, kFileSystemTypeLocal, + FileSystemMountOption(), + base::FilePath(test.path))) << "Adding mount point: " << test.name << " with path " << test.path; } @@ -135,25 +135,25 @@ scoped_refptr<ExternalMountPoints> mount_points = ExternalMountPoints::CreateRefCounted(); - mount_points->RegisterFileSystem("c", kFileSystemTypeNativeLocal, + mount_points->RegisterFileSystem("c", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/a/b/c"))); // Note that "/a/b/c" < "/a/b/c(1)" < "/a/b/c/". - mount_points->RegisterFileSystem("c(1)", kFileSystemTypeNativeLocal, + mount_points->RegisterFileSystem("c(1)", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/a/b/c(1)"))); - mount_points->RegisterFileSystem("x", kFileSystemTypeNativeLocal, + mount_points->RegisterFileSystem("x", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/z/y/x"))); - mount_points->RegisterFileSystem("o", kFileSystemTypeNativeLocal, + mount_points->RegisterFileSystem("o", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/m/n/o"))); // A mount point whose name does not match its path base name. - mount_points->RegisterFileSystem("mount", kFileSystemTypeNativeLocal, + mount_points->RegisterFileSystem("mount", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/root/foo"))); // A mount point with an empty path. - mount_points->RegisterFileSystem("empty_path", kFileSystemTypeNativeLocal, + mount_points->RegisterFileSystem("empty_path", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath()); struct TestCase { @@ -246,10 +246,9 @@ mount_points->HandlesFileSystemMountType(kFileSystemTypePersistent)); EXPECT_FALSE(mount_points->HandlesFileSystemMountType(kFileSystemTypeTest)); // Not even if it's external subtype. + EXPECT_FALSE(mount_points->HandlesFileSystemMountType(kFileSystemTypeLocal)); EXPECT_FALSE( - mount_points->HandlesFileSystemMountType(kFileSystemTypeNativeLocal)); - EXPECT_FALSE(mount_points->HandlesFileSystemMountType( - kFileSystemTypeRestrictedNativeLocal)); + mount_points->HandlesFileSystemMountType(kFileSystemTypeRestrictedLocal)); EXPECT_FALSE( mount_points->HandlesFileSystemMountType(kFileSystemTypeDriveFs)); EXPECT_FALSE( @@ -263,7 +262,7 @@ const url::Origin kTestOrigin = url::Origin::Create(GURL("http://chromium.org")); - mount_points->RegisterFileSystem("c", kFileSystemTypeNativeLocal, + mount_points->RegisterFileSystem("c", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/a/b/c"))); mount_points->RegisterFileSystem("c(1)", kFileSystemTypeDriveFs, @@ -286,7 +285,7 @@ // Try native local which is not cracked. FileSystemURL native_local = mount_points->CreateCrackedFileSystemURL( - kTestOrigin, kFileSystemTypeNativeLocal, base::FilePath(FPL("c"))); + kTestOrigin, kFileSystemTypeLocal, base::FilePath(FPL("c"))); EXPECT_FALSE(native_local.is_valid()); struct TestCase { @@ -298,8 +297,7 @@ }; const TestCase kTestCases[] = { - {FPL("c/d/e"), true, kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), - "c"}, + {FPL("c/d/e"), true, kFileSystemTypeLocal, DRIVE FPL("/a/b/c/d/e"), "c"}, {FPL("c(1)/d/e"), true, kFileSystemTypeDriveFs, DRIVE FPL("/a/b/c(1)/d/e"), "c(1)"}, {FPL("c(1)"), true, kFileSystemTypeDriveFs, DRIVE FPL("/a/b/c(1)"), "c(1)"}, @@ -320,8 +318,7 @@ {FPL("c/d/../e"), false, kFileSystemTypeUnknown, FPL(""), ""}, {FPL("/empty_path/a/../b"), false, kFileSystemTypeUnknown, FPL(""), ""}, #if defined(FILE_PATH_USES_WIN_SEPARATORS) - {FPL("c/d\\e"), true, kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), - "c"}, + {FPL("c/d\\e"), true, kFileSystemTypeLocal, DRIVE FPL("/a/b/c/d/e"), "c"}, {FPL("mount\\a\\b"), true, kFileSystemTypeDriveFs, DRIVE FPL("/root/a/b"), "mount"}, #endif @@ -361,7 +358,7 @@ const GURL kTestOrigin("http://chromium.org"); - mount_points->RegisterFileSystem("c", kFileSystemTypeNativeLocal, + mount_points->RegisterFileSystem("c", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/a/b/c"))); mount_points->RegisterFileSystem("c(1)", kFileSystemTypeDriveFs, @@ -382,8 +379,7 @@ }; const TestCase kTestCases[] = { - {FPL("c/d/e"), true, kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), - "c"}, + {FPL("c/d/e"), true, kFileSystemTypeLocal, DRIVE FPL("/a/b/c/d/e"), "c"}, {FPL("c(1)/d/e"), true, kFileSystemTypeDriveFs, DRIVE FPL("/a/b/c(1)/d/e"), "c(1)"}, {FPL("c(1)"), true, kFileSystemTypeDriveFs, DRIVE FPL("/a/b/c(1)"), "c(1)"}, @@ -404,8 +400,7 @@ {FPL("c/d/../e"), false, kFileSystemTypeUnknown, FPL(""), ""}, {FPL("/empty_path/a/../b"), false, kFileSystemTypeUnknown, FPL(""), ""}, #if defined(FILE_PATH_USES_WIN_SEPARATORS) - {FPL("c/d\\e"), true, kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), - "c"}, + {FPL("c/d\\e"), true, kFileSystemTypeLocal, DRIVE FPL("/a/b/c/d/e"), "c"}, {FPL("mount\\a\\b"), true, kFileSystemTypeDriveFs, DRIVE FPL("/root/a/b"), "mount"}, #endif @@ -445,11 +440,11 @@ ExternalMountPoints::CreateRefCounted(); mount_points->RegisterFileSystem( - "nosync", kFileSystemTypeNativeLocal, + "nosync", kFileSystemTypeLocal, FileSystemMountOption(FlushPolicy::NO_FLUSH_ON_COMPLETION), base::FilePath(DRIVE FPL("/nosync"))); mount_points->RegisterFileSystem( - "sync", kFileSystemTypeNativeLocal, + "sync", kFileSystemTypeLocal, FileSystemMountOption(FlushPolicy::FLUSH_ON_COMPLETION), base::FilePath(DRIVE FPL("/sync")));
diff --git a/storage/browser/file_system/file_system_context.cc b/storage/browser/file_system/file_system_context.cc index 0146db06..51aacf1 100644 --- a/storage/browser/file_system/file_system_context.cc +++ b/storage/browser/file_system/file_system_context.cc
@@ -90,8 +90,8 @@ case kFileSystemTypeSyncable: return FILE_PERMISSION_SANDBOX; - case kFileSystemTypeNativeForPlatformApp: - case kFileSystemTypeNativeLocal: + case kFileSystemTypeLocalForPlatformApp: + case kFileSystemTypeLocal: case kFileSystemTypeCloudDevice: case kFileSystemTypeProvided: case kFileSystemTypeDeviceMediaAsFileStorage: @@ -101,11 +101,11 @@ case kFileSystemTypeSmbFs: return FILE_PERMISSION_USE_FILE_PERMISSION; - case kFileSystemTypeRestrictedNativeLocal: + case kFileSystemTypeRestrictedLocal: return FILE_PERMISSION_READ_ONLY | FILE_PERMISSION_USE_FILE_PERMISSION; case kFileSystemTypeDeviceMedia: - case kFileSystemTypeNativeMedia: + case kFileSystemTypeLocalMedia: return FILE_PERMISSION_USE_FILE_PERMISSION; // Following types are only accessed via IsolatedFileSystem, and @@ -180,13 +180,13 @@ RegisterBackend(backend.get()); // If the embedder's additional backends already provide support for - // kFileSystemTypeNativeLocal and kFileSystemTypeNativeForPlatformApp then + // kFileSystemTypeLocal and kFileSystemTypeLocalForPlatformApp then // IsolatedFileSystemBackend does not need to handle them. For example, on // Chrome OS the additional backend chromeos::FileSystemBackend handles these // types. isolated_backend_ = std::make_unique<IsolatedFileSystemBackend>( - !base::Contains(backend_map_, kFileSystemTypeNativeLocal), - !base::Contains(backend_map_, kFileSystemTypeNativeForPlatformApp)); + !base::Contains(backend_map_, kFileSystemTypeLocal), + !base::Contains(backend_map_, kFileSystemTypeLocalForPlatformApp)); RegisterBackend(isolated_backend_.get()); if (quota_manager_proxy) {
diff --git a/storage/browser/file_system/file_system_context_unittest.cc b/storage/browser/file_system/file_system_context_unittest.cc index 0f2199e..76aa905b0 100644 --- a/storage/browser/file_system/file_system_context_unittest.cc +++ b/storage/browser/file_system/file_system_context_unittest.cc
@@ -107,12 +107,12 @@ std::string isolated_name = "root"; IsolatedContext::ScopedFSHandle isolated_fs = IsolatedContext::GetInstance()->RegisterFileSystemForPath( - kFileSystemTypeNativeLocal, std::string(), + kFileSystemTypeLocal, std::string(), base::FilePath(DRIVE FPL("/test/isolated/root")), &isolated_name); std::string isolated_id = isolated_fs.id(); // Register system external mount point. ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - "system", kFileSystemTypeNativeLocal, FileSystemMountOption(), + "system", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/test/sys/")))); FileSystemURL cracked_isolated = file_system_context->CrackURL( @@ -120,7 +120,7 @@ ExpectFileSystemURLMatches( cracked_isolated, GURL(kTestOrigin), kFileSystemTypeIsolated, - kFileSystemTypeNativeLocal, + kFileSystemTypeLocal, base::FilePath(DRIVE FPL("/test/isolated/root/file")) .NormalizePathSeparators(), base::FilePath::FromUTF8Unsafe(isolated_id) @@ -133,7 +133,7 @@ ExpectFileSystemURLMatches( cracked_external, GURL(kTestOrigin), kFileSystemTypeExternal, - kFileSystemTypeNativeLocal, + kFileSystemTypeLocal, base::FilePath(DRIVE FPL("/test/sys/root/file")) .NormalizePathSeparators(), base::FilePath(FPL("system/root/file")).NormalizePathSeparators(), @@ -150,7 +150,7 @@ // Register system external mount point. ASSERT_TRUE(mount_points->RegisterFileSystem( - "system", kFileSystemTypeNativeLocal, FileSystemMountOption(), + "system", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/test/sys/")))); scoped_refptr<FileSystemContext> file_system_context( @@ -166,7 +166,7 @@ ExpectFileSystemURLMatches( cracked_external, GURL(kTestOrigin), kFileSystemTypeExternal, - kFileSystemTypeNativeLocal, + kFileSystemTypeLocal, base::FilePath(DRIVE FPL("/test/sys/root/file")) .NormalizePathSeparators(), base::FilePath(FPL("system/root/file")).NormalizePathSeparators(), @@ -186,27 +186,27 @@ std::string isolated_file_system_name = "root"; IsolatedContext::ScopedFSHandle isolated_fs = IsolatedContext::GetInstance()->RegisterFileSystemForPath( - kFileSystemTypeNativeLocal, std::string(), + kFileSystemTypeLocal, std::string(), base::FilePath(DRIVE FPL("/test/isolated/root")), &isolated_file_system_name); const std::string kIsolatedFileSystemID = isolated_fs.id(); // Register system external mount point. ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - "system", kFileSystemTypeNativeLocal, FileSystemMountOption(), + "system", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/test/sys/")))); ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - "ext", kFileSystemTypeNativeLocal, FileSystemMountOption(), + "ext", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/test/ext")))); // Register a system external mount point with the same name/id as the // registered isolated mount point. ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - kIsolatedFileSystemID, kFileSystemTypeRestrictedNativeLocal, + kIsolatedFileSystemID, kFileSystemTypeRestrictedLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/test/system/isolated")))); // Add a mount points with the same name as a system mount point to // FileSystemContext's external mount points. ASSERT_TRUE(external_mount_points->RegisterFileSystem( - "ext", kFileSystemTypeNativeLocal, FileSystemMountOption(), + "ext", kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath(DRIVE FPL("/test/local/ext/")))); const GURL kTestOrigin = GURL("http://chromium.org/"); @@ -239,18 +239,17 @@ }, // Should be cracked by isolated mount points: {kIsolatedFileSystemID, "isolated", true /* is_valid */, - kFileSystemTypeIsolated, kFileSystemTypeNativeLocal, + kFileSystemTypeIsolated, kFileSystemTypeLocal, DRIVE FPL("/test/isolated/root/file"), kIsolatedFileSystemID}, // Should be cracked by system mount points: {"system", "external", true /* is_valid */, kFileSystemTypeExternal, - kFileSystemTypeNativeLocal, DRIVE FPL("/test/sys/root/file"), "system"}, + kFileSystemTypeLocal, DRIVE FPL("/test/sys/root/file"), "system"}, {kIsolatedFileSystemID, "external", true /* is_valid */, - kFileSystemTypeExternal, kFileSystemTypeRestrictedNativeLocal, + kFileSystemTypeExternal, kFileSystemTypeRestrictedLocal, DRIVE FPL("/test/system/isolated/root/file"), kIsolatedFileSystemID}, // Should be cracked by FileSystemContext's ExternalMountPoints. {"ext", "external", true /* is_valid */, kFileSystemTypeExternal, - kFileSystemTypeNativeLocal, DRIVE FPL("/test/local/ext/root/file"), - "ext"}, + kFileSystemTypeLocal, DRIVE FPL("/test/local/ext/root/file"), "ext"}, // Test for invalid filesystem url (made invalid by adding invalid // filesystem type). {"sytem", "external", false /* is_valid */, @@ -310,7 +309,7 @@ std::string isolated_fs_name = "root"; IsolatedContext::ScopedFSHandle isolated_fs = IsolatedContext::GetInstance()->RegisterFileSystemForPath( - kFileSystemTypeNativeLocal, std::string(), + kFileSystemTypeLocal, std::string(), base::FilePath(DRIVE FPL("/test/isolated/root")), &isolated_fs_name); std::string isolated_fs_id = isolated_fs.id(); cracked_url = @@ -321,7 +320,7 @@ // A request for an external mount point should be served. const std::string kExternalMountName = "ext_mount"; ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( - kExternalMountName, kFileSystemTypeNativeLocal, FileSystemMountOption(), + kExternalMountName, kFileSystemTypeLocal, FileSystemMountOption(), base::FilePath())); cracked_url = context->CrackURL(CreateRawFileSystemURL("external", kExternalMountName)); @@ -348,10 +347,9 @@ file_system_context->GetFileSystemBackend(kFileSystemTypeDragged)); EXPECT_TRUE(file_system_context->GetFileSystemBackend( kFileSystemTypeForTransientFile)); - EXPECT_TRUE( - file_system_context->GetFileSystemBackend(kFileSystemTypeNativeLocal)); + EXPECT_TRUE(file_system_context->GetFileSystemBackend(kFileSystemTypeLocal)); EXPECT_TRUE(file_system_context->GetFileSystemBackend( - kFileSystemTypeNativeForPlatformApp)); + kFileSystemTypeLocalForPlatformApp)); } } // namespace
diff --git a/storage/browser/file_system/isolated_context_unittest.cc b/storage/browser/file_system/isolated_context_unittest.cc index 2c28720..b8544bd 100644 --- a/storage/browser/file_system/isolated_context_unittest.cc +++ b/storage/browser/file_system/isolated_context_unittest.cc
@@ -119,7 +119,7 @@ IsolatedContext::ScopedFSHandle fs2 = isolated_context()->RegisterFileSystemForPath( - kFileSystemTypeNativeLocal, std::string(), + kFileSystemTypeLocal, std::string(), base::FilePath(DRIVE FPL("/foo")), nullptr); // Make sure the GetDraggedFileInfo returns false for both ones. @@ -133,13 +133,13 @@ // Try registering three more file systems for the same path as id2. IsolatedContext::ScopedFSHandle fs3 = isolated_context()->RegisterFileSystemForPath( - kFileSystemTypeNativeLocal, std::string(), path, nullptr); + kFileSystemTypeLocal, std::string(), path, nullptr); IsolatedContext::ScopedFSHandle fs4 = isolated_context()->RegisterFileSystemForPath( - kFileSystemTypeNativeLocal, std::string(), path, nullptr); + kFileSystemTypeLocal, std::string(), path, nullptr); IsolatedContext::ScopedFSHandle fs5 = isolated_context()->RegisterFileSystemForPath( - kFileSystemTypeNativeLocal, std::string(), path, nullptr); + kFileSystemTypeLocal, std::string(), path, nullptr); // Remove file system for id4. fs4 = IsolatedContext::ScopedFSHandle(); @@ -317,12 +317,12 @@ EXPECT_FALSE( isolated_context()->HandlesFileSystemMountType(kFileSystemTypeTest)); // Not even if it's isolated subtype. - EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( - kFileSystemTypeNativeLocal)); + EXPECT_FALSE( + isolated_context()->HandlesFileSystemMountType(kFileSystemTypeLocal)); EXPECT_FALSE( isolated_context()->HandlesFileSystemMountType(kFileSystemTypeDragged)); EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( - kFileSystemTypeNativeMedia)); + kFileSystemTypeLocalMedia)); EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( kFileSystemTypeDeviceMedia)); }
diff --git a/storage/browser/file_system/isolated_file_system_backend.cc b/storage/browser/file_system/isolated_file_system_backend.cc index a3c04881..49e2e3d 100644 --- a/storage/browser/file_system/isolated_file_system_backend.cc +++ b/storage/browser/file_system/isolated_file_system_backend.cc
@@ -53,9 +53,9 @@ case kFileSystemTypeDragged: case kFileSystemTypeForTransientFile: return true; - case kFileSystemTypeNativeLocal: + case kFileSystemTypeLocal: return use_for_type_native_local_; - case kFileSystemTypeNativeForPlatformApp: + case kFileSystemTypeLocalForPlatformApp: return use_for_type_platform_app_; default: return false; @@ -76,7 +76,7 @@ AsyncFileUtil* IsolatedFileSystemBackend::GetAsyncFileUtil( FileSystemType type) { switch (type) { - case kFileSystemTypeNativeLocal: + case kFileSystemTypeLocal: return isolated_file_util_.get(); case kFileSystemTypeDragged: return dragged_file_util_.get(); @@ -117,7 +117,7 @@ bool IsolatedFileSystemBackend::HasInplaceCopyImplementation( FileSystemType type) const { - DCHECK(type == kFileSystemTypeNativeLocal || type == kFileSystemTypeDragged || + DCHECK(type == kFileSystemTypeLocal || type == kFileSystemTypeDragged || type == kFileSystemTypeForTransientFile); return false; }
diff --git a/storage/common/file_system/file_system_types.h b/storage/common/file_system/file_system_types.h index 26eac0f..c171e0f 100644 --- a/storage/common/file_system/file_system_types.h +++ b/storage/common/file_system/file_system_types.h
@@ -54,14 +54,13 @@ // Should be used only for testing. kFileSystemTypeTest, - // Indicates a local filesystem where we can access files using native - // local path. - kFileSystemTypeNativeLocal, + // Indicates a local filesystem where we can access files using local path. + kFileSystemTypeLocal, - // Indicates a local filesystem where we can access files using native - // local path, but with restricted access. - // Restricted native local file system is in read-only mode. - kFileSystemTypeRestrictedNativeLocal, + // Indicates a local filesystem where we can access files using local path, + // but with restricted access. + // Restricted local file system is in read-only mode. + kFileSystemTypeRestrictedLocal, // Indicates a transient, isolated file system for dragged files (which could // contain multiple dragged paths in the virtual root). @@ -69,7 +68,7 @@ // Indicates media filesystem which we can access with same manner to // regular filesystem. - kFileSystemTypeNativeMedia, + kFileSystemTypeLocalMedia, // Indicates media filesystem to which we need special protocol to access, // such as MTP or PTP. @@ -88,9 +87,9 @@ // Indicates an external filesystem accessible by file paths from platform // Apps. As of writing, on non Chrome OS platform, this is merely a - // kFileSystemTypeNativeLocal. On Chrome OS, the path is parsed by + // kFileSystemTypeLocal. On Chrome OS, the path is parsed by // the handlers of kFileSystemTypeExternal. - kFileSystemTypeNativeForPlatformApp, + kFileSystemTypeLocalForPlatformApp, // Indicates an isolated filesystem which is supposed to contain one // temporary which is supposed to go away when the last reference of
diff --git a/storage/common/file_system/file_system_util.cc b/storage/common/file_system/file_system_util.cc index 3bead66..0e22469 100644 --- a/storage/common/file_system/file_system_util.cc +++ b/storage/common/file_system/file_system_util.cc
@@ -282,21 +282,21 @@ return "External"; case kFileSystemTypeTest: return "Test"; - case kFileSystemTypeNativeLocal: - return "NativeLocal"; - case kFileSystemTypeRestrictedNativeLocal: - return "RestrictedNativeLocal"; + case kFileSystemTypeLocal: + return "Local"; + case kFileSystemTypeRestrictedLocal: + return "RestrictedLocal"; case kFileSystemTypeDragged: return "Dragged"; - case kFileSystemTypeNativeMedia: - return "NativeMedia"; + case kFileSystemTypeLocalMedia: + return "LocalMedia"; case kFileSystemTypeDeviceMedia: return "DeviceMedia"; case kFileSystemTypeSyncable: case kFileSystemTypeSyncableForInternalSync: return "Syncable"; - case kFileSystemTypeNativeForPlatformApp: - return "NativeForPlatformApp"; + case kFileSystemTypeLocalForPlatformApp: + return "LocalForPlatformApp"; case kFileSystemTypeForTransientFile: return "TransientFile"; case kFileSystemTypePluginPrivate:
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json index df088176..f1ecfa1 100644 --- a/testing/buildbot/chromium.android.fyi.json +++ b/testing/buildbot/chromium.android.fyi.json
@@ -336,11 +336,11 @@ "--bucket", "chromium-result-details", "--test-name", - "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.23" + "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.25" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" }, - "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.23", + "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.25", "resultdb": { "enable": true }, @@ -350,7 +350,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M89", - "revision": "version:89.0.4389.23" + "revision": "version:89.0.4389.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -573,11 +573,11 @@ "--bucket", "chromium-result-details", "--test-name", - "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.23" + "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.25" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" }, - "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.23", + "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.25", "resultdb": { "enable": true }, @@ -587,7 +587,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M89", - "revision": "version:89.0.4389.23" + "revision": "version:89.0.4389.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -814,11 +814,11 @@ "--bucket", "chromium-result-details", "--test-name", - "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.23" + "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.25" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" }, - "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.23", + "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.25", "resultdb": { "enable": true }, @@ -828,7 +828,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M89", - "revision": "version:89.0.4389.23" + "revision": "version:89.0.4389.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -1051,11 +1051,11 @@ "--bucket", "chromium-result-details", "--test-name", - "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.23" + "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.25" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" }, - "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.23", + "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.25", "resultdb": { "enable": true }, @@ -1065,7 +1065,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M89", - "revision": "version:89.0.4389.23" + "revision": "version:89.0.4389.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -1355,11 +1355,11 @@ "--bucket", "chromium-result-details", "--test-name", - "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.23" + "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.25" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" }, - "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.23", + "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.25", "resultdb": { "enable": true }, @@ -1369,7 +1369,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M89", - "revision": "version:89.0.4389.23" + "revision": "version:89.0.4389.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -1592,11 +1592,11 @@ "--bucket", "chromium-result-details", "--test-name", - "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.23" + "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.25" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" }, - "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.23", + "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.25", "resultdb": { "enable": true }, @@ -1606,7 +1606,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M89", - "revision": "version:89.0.4389.23" + "revision": "version:89.0.4389.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -1896,11 +1896,11 @@ "--bucket", "chromium-result-details", "--test-name", - "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.23" + "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.25" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" }, - "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.23", + "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 89.0.4389.25", "resultdb": { "enable": true }, @@ -1910,7 +1910,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M89", - "revision": "version:89.0.4389.23" + "revision": "version:89.0.4389.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -2133,11 +2133,11 @@ "--bucket", "chromium-result-details", "--test-name", - "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.23" + "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.25" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" }, - "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.23", + "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 89.0.4389.25", "resultdb": { "enable": true }, @@ -2147,7 +2147,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M89", - "revision": "version:89.0.4389.23" + "revision": "version:89.0.4389.25" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json index 52d08780..5f25cc6 100644 --- a/testing/buildbot/chromium.mac.json +++ b/testing/buildbot/chromium.mac.json
@@ -3789,27 +3789,6 @@ "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/" }, { - "experiment_percentage": 10, - "isolate_profile_data": true, - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "gpu": "none", - "os": "Mac-10.13.6" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", - "shards": 40 - }, - "test": "browser_tests", - "test_id_prefix": "ninja://chrome/test:browser_tests/" - }, - { "args": [ "--gtest_filter=-*UsingRealWebcam*" ],
diff --git a/testing/buildbot/filters/cast-linux.content_browsertests.filter b/testing/buildbot/filters/cast-linux.content_browsertests.filter index 141a6b7..5a2575b 100644 --- a/testing/buildbot/filters/cast-linux.content_browsertests.filter +++ b/testing/buildbot/filters/cast-linux.content_browsertests.filter
@@ -1,3 +1,2 @@ # crbug.com/503735: Some pepper font-related tests fail on cast_shell trybot --OutOfProcessPPAPITest.TrueTypeFont -OutOfProcessPPAPITest.BrowserFont \ No newline at end of file
diff --git a/testing/buildbot/filters/lacros.browser_tests.filter b/testing/buildbot/filters/lacros.browser_tests.filter index 224d619c..aa99fa3 100644 --- a/testing/buildbot/filters/lacros.browser_tests.filter +++ b/testing/buildbot/filters/lacros.browser_tests.filter
@@ -136,8 +136,6 @@ -PictureInPictureWindowControllerBrowserTest.* -PopupTrackerBrowserTest.PopupInWindow_IsWindowTrue -PopupTrackerBrowserTest.ShiftClick_HasTracker --PPAPINaClNewlibTest.TrueTypeFont --PPAPINaClPNaClNonSfiTest.TrueTypeFont -PresentationReceiverWindowControllerBrowserTest.CreatesWindow -PreservedWindowPlacement.Test -PrintPreviewDestinationSelectTest.ChangeIconDeprecationWarnings
diff --git a/testing/buildbot/filters/lacros.content_browsertests.filter b/testing/buildbot/filters/lacros.content_browsertests.filter index 81c99ff9..a04c255 100644 --- a/testing/buildbot/filters/lacros.content_browsertests.filter +++ b/testing/buildbot/filters/lacros.content_browsertests.filter
@@ -1,6 +1,4 @@ - # TODO(crbug.com/1111979) Enable all tests on lacros. --OutOfProcessPPAPITest.TrueTypeFont # Following tests are flaky. -All/WebContentsVideoCaptureDeviceBrowserTestP.CapturesContentChanges*
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl index 94b0e5f..1e17a93 100644 --- a/testing/buildbot/test_suite_exceptions.pyl +++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -441,6 +441,7 @@ 'CrWinAsan(dll)', # https://crbug.com/935598 'linux-win_cross-rel', 'ToTLinuxTSan', # https://crbug.com/368525 + 'Mac10.13 Tests', # https://crbug.com/1042757 'Linux TSan Tests', # https://crbug.com/368525 'Win10 Tests x64 (dbg)', ], @@ -510,12 +511,6 @@ 'shards': 30, }, }, - 'Mac10.13 Tests':{ - 'experiment_percentage': 10, # https://crbug.com/1042757 - 'swarming': { - 'shards': 40, - }, - }, 'Mac10.13 Tests (dbg)': { # https://crbug.com/1152770 'swarming': {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl index 131e88c..0295dfbd 100644 --- a/testing/buildbot/variants.pyl +++ b/testing/buildbot/variants.pyl
@@ -320,13 +320,13 @@ '../../weblayer/browser/android/javatests/skew/expectations.txt', '--impl-version=89', ], - 'identifier': 'Implementation Tests For 89.0.4389.23', + 'identifier': 'Implementation Tests For 89.0.4389.25', 'swarming': { 'cipd_packages': [ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M89', - 'revision': 'version:89.0.4389.23', + 'revision': 'version:89.0.4389.25', } ], }, @@ -392,13 +392,13 @@ '../../weblayer/browser/android/javatests/skew/expectations.txt', '--impl-version=89', ], - 'identifier': 'Implementation Tests For 89.0.4389.23', + 'identifier': 'Implementation Tests For 89.0.4389.25', 'swarming': { 'cipd_packages': [ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M89', - 'revision': 'version:89.0.4389.23', + 'revision': 'version:89.0.4389.25', } ], }, @@ -464,13 +464,13 @@ '../../weblayer/browser/android/javatests/skew/expectations.txt', '--client-version=89', ], - 'identifier': 'Client Tests For 89.0.4389.23', + 'identifier': 'Client Tests For 89.0.4389.25', 'swarming': { 'cipd_packages': [ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M89', - 'revision': 'version:89.0.4389.23', + 'revision': 'version:89.0.4389.25', } ], }, @@ -538,4 +538,4 @@ }, 'identifier': 'OCTOPUS_TOT-1', }, -} +} \ No newline at end of file
diff --git a/testing/test.gni b/testing/test.gni index a9a0ece..a522190 100644 --- a/testing/test.gni +++ b/testing/test.gni
@@ -16,8 +16,10 @@ import("//build/config/sanitizers/sanitizers.gni") } else if (is_fuchsia) { import("//build/config/chromecast_build.gni") + import("//build/config/coverage/coverage.gni") import("//build/config/fuchsia/generate_runner_scripts.gni") import("//build/config/fuchsia/package.gni") + import("//third_party/fuchsia-sdk/sdk/build/cmc.gni") } else if (is_chromeos_ash) { import("//build/config/chromeos/rules.gni") import("//build/config/sanitizers/sanitizers.gni") @@ -245,6 +247,26 @@ _pkg_target = "${_output_name}_pkg" _exec_target = "${_output_name}__exec" + # TODO(1019938): switch the default to tests.cmx which doesn't request + # the deprecated-ambient-replace-as-executable feature. + if (!defined(invoker.manifest)) { + manifest = "//build/config/fuchsia/tests-with-exec.cmx" + } else { + manifest = invoker.manifest + } + + if (use_clang_coverage) { + component_with_coverage_manifest = "${target_name}-coverage.test-cmx" + cmc_merge(component_with_coverage_manifest) { + sources = [ + "//build/config/fuchsia/add_DebugData_service.test-cmx", + manifest, + ] + output_name = target_name + } + manifest = "${target_out_dir}/${component_with_coverage_manifest}" + } + fuchsia_package_runner(target_name) { is_test_exe = true forward_variables_from(invoker, @@ -261,9 +283,11 @@ } cr_fuchsia_package(_pkg_target) { - forward_variables_from(invoker, [ "manifest" ]) binary = ":$_exec_target" package_name_override = _output_name + if (use_clang_coverage) { + deps = [ ":$component_with_coverage_manifest" ] + } } executable(_exec_target) {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 372bba0..bc175d3d 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -1936,13 +1936,40 @@ ], "experiments": [ { - "name": "Enabled", + "name": "PSIThreshold_10_Percent", + "params": { + "CrOSLowMemoryPSIThreshold": "10" + }, + "enable_features": [ + "CrOSLowMemoryNotificationPSI" + ] + }, + { + "name": "PSIThreshold_20_Percent", "params": { "CrOSLowMemoryPSIThreshold": "20" }, "enable_features": [ "CrOSLowMemoryNotificationPSI" ] + }, + { + "name": "PSIThreshold_30_Percent", + "params": { + "CrOSLowMemoryPSIThreshold": "30" + }, + "enable_features": [ + "CrOSLowMemoryNotificationPSI" + ] + }, + { + "name": "PSIThreshold_40_Percent", + "params": { + "CrOSLowMemoryPSIThreshold": "40" + }, + "enable_features": [ + "CrOSLowMemoryNotificationPSI" + ] } ] }
diff --git a/third_party/androidx/build.gradle.template b/third_party/androidx/build.gradle.template index dd7b0c7f..b1ec2ad 100644 --- a/third_party/androidx/build.gradle.template +++ b/third_party/androidx/build.gradle.template
@@ -23,6 +23,7 @@ compile "androidx.lifecycle:lifecycle-livedata:{{androidx_dependency_version}}" compile "androidx.lifecycle:lifecycle-livedata-core:{{androidx_dependency_version}}" compile "androidx.lifecycle:lifecycle-viewmodel:{{androidx_dependency_version}}" + compile "androidx.lifecycle:lifecycle-viewmodel-savedstate:{{androidx_dependency_version}}" compile "androidx.core:core:{{androidx_dependency_version}}" compile "androidx.core:core-animation:{{androidx_dependency_version}}" @@ -56,6 +57,7 @@ compile "androidx.tvprovider:tvprovider:{{androidx_dependency_version}}" compile "androidx.viewpager:viewpager:{{androidx_dependency_version}}" compile "androidx.exifinterface:exifinterface:{{androidx_dependency_version}}" + compile "androidx.window:window:{{androidx_dependency_version}}" // Those are for use by doubledown libraries. compile "androidx.arch.core:core-common:{{androidx_dependency_version}}"
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom index a5eb64e..60a41ec 100644 --- a/third_party/blink/public/mojom/web_feature/web_feature.mojom +++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -3002,8 +3002,8 @@ kUndeferrableThirdPartySubresourceRequestWithCookie = 3682, kXRDepthSensing = 3683, kXRFrameGetDepthInformation = 3684, - kXRDepthInformationGetDepth = 3685, - kXRDepthInformationDataAttribute = 3686, + kXRCPUDepthInformationGetDepth = 3685, + kXRCPUDepthInformationDataAttribute = 3686, kInterestCohortAPI_interestCohort_Method = 3687, kOBSOLETE_AddressSpaceLocalEmbeddedInPrivateSecureContext = 3688, kOBSOLETE_AddressSpaceLocalEmbeddedInPrivateNonSecureContext = 3689, @@ -3111,6 +3111,8 @@ kRTCPeerConnectionUsingComplexUnifiedPlan = 3788, kWindowScreenIsExtended = 3789, kWindowScreenChange = 3790, + kXRWebGLDepthInformationTextureAttribute = 3791, + kXRWebGLBindingGetDepthInformation = 3792, // Add new features immediately above this line. Don't change assigned // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/web/web_media_inspector.h b/third_party/blink/public/web/web_media_inspector.h index 5a8d99f..1af4bbc 100644 --- a/third_party/blink/public/web/web_media_inspector.h +++ b/third_party/blink/public/web/web_media_inspector.h
@@ -43,7 +43,8 @@ public: virtual WebString CreatePlayer() = 0; - // These methods DCHECK if the player id is invalid. + virtual void DestroyPlayer(const WebString& playerId) = 0; + virtual void NotifyPlayerEvents(WebString player_id, const InspectorPlayerEvents&) = 0; virtual void NotifyPlayerErrors(WebString player_id,
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni index 099be40..06a5e19 100644 --- a/third_party/blink/renderer/bindings/generated_in_modules.gni +++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -851,6 +851,8 @@ "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_websocket_stream_options.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_write_params.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_write_params.h", + "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_depth_state_init.cc", + "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_depth_state_init.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_dom_overlay_init.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_dom_overlay_init.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_hit_test_options_init.cc", @@ -2218,6 +2220,10 @@ "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_bounded_reference_space.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_depth_information.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_depth_information.h", + "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_cpu_depth_information.cc", + "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_cpu_depth_information.h", + "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_webgl_depth_information.cc", + "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_webgl_depth_information.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_dom_overlay_state.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_dom_overlay_state.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_frame.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni index 5dd8326..de7a1ae 100644 --- a/third_party/blink/renderer/bindings/idl_in_modules.gni +++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -1006,7 +1006,9 @@ "//third_party/blink/renderer/modules/xr/xr_anchor.idl", "//third_party/blink/renderer/modules/xr/xr_anchor_set.idl", "//third_party/blink/renderer/modules/xr/xr_bounded_reference_space.idl", + "//third_party/blink/renderer/modules/xr/xr_cpu_depth_information.idl", "//third_party/blink/renderer/modules/xr/xr_depth_information.idl", + "//third_party/blink/renderer/modules/xr/xr_depth_state_init.idl", "//third_party/blink/renderer/modules/xr/xr_dom_overlay_init.idl", "//third_party/blink/renderer/modules/xr/xr_dom_overlay_state.idl", "//third_party/blink/renderer/modules/xr/xr_frame.idl", @@ -1054,6 +1056,7 @@ "//third_party/blink/renderer/modules/xr/xr_viewport.idl", "//third_party/blink/renderer/modules/xr/xr_webgl_binding.idl", "//third_party/blink/renderer/modules/xr/xr_webgl_context.idl", + "//third_party/blink/renderer/modules/xr/xr_webgl_depth_information.idl", "//third_party/blink/renderer/modules/xr/xr_webgl_layer.idl", "//third_party/blink/renderer/modules/xr/xr_webgl_layer_init.idl", ],
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn index 24777b6c..f20764b 100644 --- a/third_party/blink/renderer/core/BUILD.gn +++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1297,6 +1297,7 @@ "input/touch_event_manager_test.cc", "inspector/inspector_emulation_agent_test.cc", "inspector/inspector_history_test.cc", + "inspector/inspector_media_context_impl_unittest.cc", "inspector/inspector_session_state_test.cc", "inspector/inspector_style_resolver_test.cc", "inspector/main_thread_debugger_test.cc", @@ -1344,6 +1345,7 @@ "layout/line/abstract_inline_text_box_test.cc", "layout/line/inline_text_box_test.cc", "layout/line/line_orientation_utils_test.cc", + "layout/list_marker_test.cc", "layout/map_coordinates_test.cc", "layout/min_max_size_test.cc", "layout/multi_column_fragmentainer_group_test.cc",
diff --git a/third_party/blink/renderer/core/css/counter_style.cc b/third_party/blink/renderer/core/css/counter_style.cc index 51a70d8..7562dcc 100644 --- a/third_party/blink/renderer/core/css/counter_style.cc +++ b/third_party/blink/renderer/core/css/counter_style.cc
@@ -388,6 +388,8 @@ } String CounterStyle::GenerateRepresentation(int value) const { + DCHECK(!IsDirty()); + if (pad_length_ > kCounterLengthLimit) return GenerateFallbackRepresentation(value);
diff --git a/third_party/blink/renderer/core/css/counter_style_map.cc b/third_party/blink/renderer/core/css/counter_style_map.cc index f5063351..352cd22 100644 --- a/third_party/blink/renderer/core/css/counter_style_map.cc +++ b/third_party/blink/renderer/core/css/counter_style_map.cc
@@ -82,12 +82,21 @@ } void CounterStyleMap::AddCounterStyles(const RuleSet& rule_set) { + if (!rule_set.CounterStyleRules().size()) + return; + for (StyleRuleCounterStyle* rule : rule_set.CounterStyleRules()) { CounterStyle* counter_style = CounterStyle::Create(*rule); if (!counter_style) continue; + AtomicString name = rule->GetName(); + if (CounterStyle* replaced = counter_styles_.at(name)) + replaced->SetIsDirty(); counter_styles_.Set(rule->GetName(), counter_style); } + + if (owner_document_) + owner_document_->GetStyleEngine().MarkCounterStylesNeedUpdate(); } CounterStyleMap* CounterStyleMap::GetAncestorMap() const { @@ -264,9 +273,15 @@ } void CounterStyleMap::Dispose() { + if (!counter_styles_.size()) + return; + for (CounterStyle* counter_style : counter_styles_.Values()) counter_style->SetIsDirty(); counter_styles_.clear(); + + if (owner_document_) + owner_document_->GetStyleEngine().MarkCounterStylesNeedUpdate(); } void CounterStyleMap::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc index 63c8414..dd570c4e 100644 --- a/third_party/blink/renderer/core/css/style_engine.cc +++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -542,11 +542,17 @@ } if (RuntimeEnabledFeatures::CSSAtRuleCounterStyleEnabled()) { - // TODO(crbug.com/687225): Add a flag to indicate whether counter styles - // need updates, so that we don't update them every time. - CounterStyleMap::MarkAllDirtyCounterStyles(GetDocument(), - active_tree_scopes_); - CounterStyleMap::ResolveAllReferences(GetDocument(), active_tree_scopes_); + // TODO(crbug.com/687225): We initialize the predefined counter styles here. + // Moving the initialization to other places causes test failures, which + // needs investigation and fixing. + CounterStyleMap::GetUACounterStyleMap(); + + if (counter_styles_need_update_) { + CounterStyleMap::MarkAllDirtyCounterStyles(GetDocument(), + active_tree_scopes_); + CounterStyleMap::ResolveAllReferences(GetDocument(), active_tree_scopes_); + counter_styles_need_update_ = false; + } } probe::ActiveStyleSheetsUpdated(document_); @@ -858,6 +864,7 @@ root->MarkSubtreeNeedsStyleRecalcForFontUpdates(); } + // TODO(xiaochengh): Move layout invalidation after style update. if (LayoutView* layout_view = GetDocument().GetLayoutView()) { TRACE_EVENT0("blink", "LayoutObject::InvalidateSubtreeForFontUpdates"); layout_view->InvalidateSubtreeLayoutForFontUpdates(); @@ -869,6 +876,13 @@ GetDocument().ScheduleLayoutTreeUpdateIfNeeded(); } +void StyleEngine::MarkCounterStylesNeedUpdate() { + counter_styles_need_update_ = true; + if (LayoutView* layout_view = GetDocument().GetLayoutView()) + layout_view->SetNeedsMarkerOrCounterUpdate(); + GetDocument().ScheduleLayoutTreeUpdateIfNeeded(); +} + void StyleEngine::FontsNeedUpdate(FontSelector*, FontInvalidationReason) { if (!GetDocument().IsActive()) return; @@ -1572,7 +1586,7 @@ EnsureUserCounterStyleMap().AddCounterStyles(*it->second); } - // TODO(crbug.com/687225): Trigger style/Layout invalidations. + MarkCounterStylesNeedUpdate(); } if (changed_rule_flags & (kPropertyRules | kScrollTimelineRules)) { @@ -1638,7 +1652,8 @@ if (changed_rule_flags & kKeyframesRules) ScopedStyleResolver::KeyframesRulesAdded(tree_scope); - // TODO(crbug.com/687725): Style/layout invalidation for counter style rules. + if (changed_rule_flags & kCounterStyleRules) + MarkCounterStylesNeedUpdate(); if ((changed_rule_flags & kPropertyRules) || rebuild_at_property_registry) { // @property rules are (for now) ignored in shadow trees, per spec.
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h index 3e534ba..16f6c6c2 100644 --- a/third_party/blink/renderer/core/css/style_engine.h +++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -377,6 +377,8 @@ void MarkFontsNeedUpdate(); void InvalidateStyleAndLayoutForFontUpdates(); + void MarkCounterStylesNeedUpdate(); + StyleRuleKeyframes* KeyframeStylesForAnimation( const AtomicString& animation_name); StyleRuleScrollTimeline* FindScrollTimelineRule(const AtomicString& name); @@ -599,6 +601,7 @@ bool in_dom_removal_{false}; bool viewport_style_dirty_{false}; bool fonts_need_update_{false}; + bool counter_styles_need_update_{false}; // Set to true if we allow marking style dirty from style recalc. Ideally, we // should get rid of this, but we keep track of where we allow it with
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc index e1fb80f5..6f56eeb 100644 --- a/third_party/blink/renderer/core/dom/document.cc +++ b/third_party/blink/renderer/core/dom/document.cc
@@ -2461,7 +2461,7 @@ PropagateStyleToViewport(); - View()->UpdateCountersAfterStyleChange(); + GetLayoutView()->UpdateMarkersAndCountersAfterStyleChange(); GetLayoutView()->RecalcLayoutOverflow(); DCHECK(!NeedsStyleRecalc());
diff --git a/third_party/blink/renderer/core/frame/fullscreen_controller.h b/third_party/blink/renderer/core/frame/fullscreen_controller.h index 61a5c81..8e65226 100644 --- a/third_party/blink/renderer/core/frame/fullscreen_controller.h +++ b/third_party/blink/renderer/core/frame/fullscreen_controller.h
@@ -37,6 +37,7 @@ #include "third_party/blink/renderer/platform/geometry/float_point.h" #include "third_party/blink/renderer/platform/geometry/int_size.h" #include "third_party/blink/renderer/platform/graphics/color.h" +#include "third_party/blink/renderer/platform/heap/heap_allocator.h" #include "third_party/blink/renderer/platform/heap/persistent.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc index a5814bb3..0e493f4a 100644 --- a/third_party/blink/renderer/core/frame/local_frame_view.cc +++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -579,12 +579,6 @@ } } -void LocalFrameView::UpdateCountersAfterStyleChange() { - auto* layout_view = GetLayoutView(); - DCHECK(layout_view); - layout_view->UpdateCounters(); -} - void LocalFrameView::CountObjectsNeedingLayout(unsigned& needs_layout_objects, unsigned& total_objects, bool& is_subtree) {
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h index 055d99f2..bbff09c 100644 --- a/third_party/blink/renderer/core/frame/local_frame_view.h +++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -255,8 +255,6 @@ bool GetIntrinsicSizingInfo(IntrinsicSizingInfo&) const override; bool HasIntrinsicSizingInfo() const override; - void UpdateCountersAfterStyleChange(); - void Dispose() override; void PropagateFrameRects() override; void InvalidateAllCustomScrollbarsOnActiveChanged();
diff --git a/third_party/blink/renderer/core/html/forms/html_opt_group_element.h b/third_party/blink/renderer/core/html/forms/html_opt_group_element.h index a03db9b..25d52a7 100644 --- a/third_party/blink/renderer/core/html/forms/html_opt_group_element.h +++ b/third_party/blink/renderer/core/html/forms/html_opt_group_element.h
@@ -37,6 +37,7 @@ public: explicit HTMLOptGroupElement(Document&); + ~HTMLOptGroupElement() override; bool IsDisabledFormControl() const override; String DefaultToolTip() const override; @@ -49,8 +50,6 @@ static bool CanAssignToOptGroupSlot(const Node&); private: - ~HTMLOptGroupElement() override; - bool SupportsFocus() const override; void ChildrenChanged(const ChildrenChange& change) override; bool ChildrenChangedAllChildrenRemovedNeedsList() const override;
diff --git a/third_party/blink/renderer/core/html/html_area_element.h b/third_party/blink/renderer/core/html/html_area_element.h index b513888..863d489 100644 --- a/third_party/blink/renderer/core/html/html_area_element.h +++ b/third_party/blink/renderer/core/html/html_area_element.h
@@ -39,6 +39,7 @@ public: explicit HTMLAreaElement(Document&); + ~HTMLAreaElement() override; bool IsDefault() const { return shape_ == kDefault; } @@ -58,8 +59,6 @@ HTMLImageElement* ImageElement() const; private: - ~HTMLAreaElement() override; - void ParseAttribute(const AttributeModificationParams&) override; bool IsKeyboardFocusable() const override; bool IsMouseFocusable() const override;
diff --git a/third_party/blink/renderer/core/html/html_meter_element.h b/third_party/blink/renderer/core/html/html_meter_element.h index 620f2cb..ab6ddfc 100644 --- a/third_party/blink/renderer/core/html/html_meter_element.h +++ b/third_party/blink/renderer/core/html/html_meter_element.h
@@ -33,6 +33,7 @@ public: explicit HTMLMeterElement(Document&); + ~HTMLMeterElement() override; enum GaugeRegion { kGaugeRegionOptimum, @@ -66,8 +67,6 @@ void Trace(Visitor*) const override; private: - ~HTMLMeterElement() override; - bool AreAuthorShadowsAllowed() const override { return false; } bool IsLabelable() const override { return true; }
diff --git a/third_party/blink/renderer/core/html/html_progress_element.h b/third_party/blink/renderer/core/html/html_progress_element.h index 61bce3b..756bf25 100644 --- a/third_party/blink/renderer/core/html/html_progress_element.h +++ b/third_party/blink/renderer/core/html/html_progress_element.h
@@ -37,6 +37,7 @@ static const double kInvalidPosition; explicit HTMLProgressElement(Document&); + ~HTMLProgressElement() override; double value() const; void setValue(double); @@ -51,8 +52,6 @@ void Trace(Visitor*) const override; private: - ~HTMLProgressElement() override; - bool AreAuthorShadowsAllowed() const override { return false; } bool ShouldAppearIndeterminate() const override; bool IsLabelable() const override { return true; }
diff --git a/third_party/blink/renderer/core/html/track/html_track_element.h b/third_party/blink/renderer/core/html/track/html_track_element.h index eb01eb5..9c207bb 100644 --- a/third_party/blink/renderer/core/html/track/html_track_element.h +++ b/third_party/blink/renderer/core/html/track/html_track_element.h
@@ -42,6 +42,7 @@ public: explicit HTMLTrackElement(Document&); + ~HTMLTrackElement() override; const AtomicString& kind(); void setKind(const AtomicString&); @@ -55,8 +56,6 @@ void Trace(Visitor*) const override; private: - ~HTMLTrackElement() override; - void ParseAttribute(const AttributeModificationParams&) override; InsertionNotificationRequest InsertedInto(ContainerNode&) override;
diff --git a/third_party/blink/renderer/core/inspector/inspector_media_agent.cc b/third_party/blink/renderer/core/inspector/inspector_media_agent.cc index 77acdfa..4993ce6 100644 --- a/third_party/blink/renderer/core/inspector/inspector_media_agent.cc +++ b/third_party/blink/renderer/core/inspector/inspector_media_agent.cc
@@ -81,7 +81,7 @@ } // namespace InspectorMediaAgent::InspectorMediaAgent(InspectedFrames* inspected_frames) - : local_frame_(inspected_frames->Root()), + : frame_(inspected_frames->Root()), enabled_(&agent_state_, /*default_value = */ false) {} InspectorMediaAgent::~InspectorMediaAgent() = default; @@ -95,8 +95,8 @@ void InspectorMediaAgent::RegisterAgent() { instrumenting_agents_->AddInspectorMediaAgent(this); auto* cache = MediaInspectorContextImpl::From( - *local_frame_->DomWindow()->GetExecutionContext()); - Vector<WebString> players = cache->AllPlayerIds(); + *frame_->DomWindow()->GetExecutionContext()); + Vector<WebString> players = cache->AllPlayerIdsAndMarkSent(); PlayersCreated(players); for (const auto& player_id : players) { const auto& media_player = cache->MediaPlayerFromId(player_id); @@ -165,7 +165,7 @@ } void InspectorMediaAgent::Trace(Visitor* visitor) const { - visitor->Trace(local_frame_); + visitor->Trace(frame_); InspectorBaseAgent::Trace(visitor); }
diff --git a/third_party/blink/renderer/core/inspector/inspector_media_agent.h b/third_party/blink/renderer/core/inspector/inspector_media_agent.h index 830a3b9..f15aaa04 100644 --- a/third_party/blink/renderer/core/inspector/inspector_media_agent.h +++ b/third_party/blink/renderer/core/inspector/inspector_media_agent.h
@@ -46,7 +46,7 @@ private: void RegisterAgent(); - Member<LocalFrame> local_frame_; + Member<LocalFrame> frame_; InspectorAgentState::Boolean enabled_; DISALLOW_COPY_AND_ASSIGN(InspectorMediaAgent); };
diff --git a/third_party/blink/renderer/core/inspector/inspector_media_context_impl.cc b/third_party/blink/renderer/core/inspector/inspector_media_context_impl.cc index 0223f9b..a7ff21b 100644 --- a/third_party/blink/renderer/core/inspector/inspector_media_context_impl.cc +++ b/third_party/blink/renderer/core/inspector/inspector_media_context_impl.cc
@@ -49,11 +49,11 @@ visitor->Trace(players_); } -Vector<WebString> MediaInspectorContextImpl::AllPlayerIds() { +Vector<WebString> MediaInspectorContextImpl::AllPlayerIdsAndMarkSent() { Vector<WebString> existing_players; - existing_players.ReserveCapacity(players_.size()); - for (const auto& player_id : players_.Keys()) - existing_players.push_back(player_id); + const auto& keys = players_.Keys(); + existing_players.AppendRange(keys.begin(), keys.end()); + unsent_players_.clear(); return existing_players; } @@ -69,16 +69,100 @@ String::FromUTF8(base::UnguessableToken::Create().ToString()); players_.insert(next_player_id, MakeGarbageCollected<MediaPlayer>()); probe::PlayersCreated(GetSupplementable(), {next_player_id}); + if (!GetSupplementable()->GetProbeSink()->HasInspectorMediaAgents()) + unsent_players_.push_back(next_player_id); return next_player_id; } +void MediaInspectorContextImpl::RemovePlayer(const WebString& playerId) { + const auto& player = players_.Take(playerId); + if (player) { + total_event_count_ -= + player->errors.size() + player->events.size() + player->messages.size(); + DCHECK_GE(total_event_count_, 0); + } +} + +void MediaInspectorContextImpl::TrimPlayer(const WebString& playerId) { + MediaPlayer* player = players_.Take(playerId); + wtf_size_t overage = total_event_count_ - kMaxCachedPlayerEvents; + + wtf_size_t excess = std::min<wtf_size_t>(overage, player->events.size()); + player->events.EraseAt(0, excess); + total_event_count_ -= excess; + overage -= excess; + + excess = std::min(overage, player->messages.size()); + player->messages.EraseAt(0, excess); + total_event_count_ -= excess; + overage -= excess; + + excess = std::min(overage, player->errors.size()); + player->errors.EraseAt(0, excess); + total_event_count_ -= excess; + overage -= excess; + + players_.insert(playerId, player); +} + +void MediaInspectorContextImpl::CullPlayers(const WebString& prefer_keep) { + // Erase all the dead players, but only erase the required number of others. + for (const auto& playerId : dead_players_) + RemovePlayer(playerId); + dead_players_.clear(); + + while (!expendable_players_.IsEmpty()) { + if (total_event_count_ <= kMaxCachedPlayerEvents) + return; + RemovePlayer(expendable_players_.back()); + expendable_players_.pop_back(); + } + + while (!unsent_players_.IsEmpty()) { + if (total_event_count_ <= kMaxCachedPlayerEvents) + return; + RemovePlayer(unsent_players_.back()); + unsent_players_.pop_back(); + } + + // TODO(tmathmeyer) keep last event time stamps for players to remove the + // most stale one. + while (players_.size() > 1) { + if (total_event_count_ <= kMaxCachedPlayerEvents) + return; + auto iterator = players_.begin(); + // Make sure not to delete the item that is preferred to keep. + if (WTF::String(prefer_keep) == iterator->key) + ++iterator; + RemovePlayer(iterator->key); + } + + // When there is only one player, selectively remove the oldest events. + if (players_.size() == 1 && total_event_count_ > kMaxCachedPlayerEvents) + TrimPlayer(players_.begin()->key); +} + +void MediaInspectorContextImpl::DestroyPlayer(const WebString& playerId) { + if (unsent_players_.Contains(String(playerId))) { + // unsent players become dead when destroyed. + unsent_players_.EraseAt(unsent_players_.Find(String(playerId))); + dead_players_.push_back(playerId); + } else { + expendable_players_.push_back(playerId); + } +} + // Convert public version of event to protocol version, and send it. void MediaInspectorContextImpl::NotifyPlayerErrors( WebString playerId, const InspectorPlayerErrors& errors) { const auto& player = players_.find(playerId); - DCHECK_NE(player, players_.end()); - player->value->errors.AppendRange(errors.begin(), errors.end()); + if (player != players_.end()) { + player->value->errors.AppendRange(errors.begin(), errors.end()); + total_event_count_ += errors.size(); + if (total_event_count_ > kMaxCachedPlayerEvents) + CullPlayers(playerId); + } Vector<InspectorPlayerError> vector = Iter2Vector<InspectorPlayerError>(errors); @@ -89,8 +173,12 @@ WebString playerId, const InspectorPlayerEvents& events) { const auto& player = players_.find(playerId); - DCHECK_NE(player, players_.end()); - player->value->events.AppendRange(events.begin(), events.end()); + if (player != players_.end()) { + player->value->events.AppendRange(events.begin(), events.end()); + total_event_count_ += events.size(); + if (total_event_count_ > kMaxCachedPlayerEvents) + CullPlayers(playerId); + } Vector<InspectorPlayerEvent> vector = Iter2Vector<InspectorPlayerEvent>(events); @@ -101,9 +189,10 @@ WebString playerId, const InspectorPlayerProperties& props) { const auto& player = players_.find(playerId); - DCHECK_NE(player, players_.end()); - for (const auto& property : props) - player->value->properties.insert(property.name, property); + if (player != players_.end()) { + for (const auto& property : props) + player->value->properties.insert(property.name, property); + } Vector<InspectorPlayerProperty> vector = Iter2Vector<InspectorPlayerProperty>(props); @@ -114,12 +203,21 @@ WebString playerId, const InspectorPlayerMessages& messages) { const auto& player = players_.find(playerId); - DCHECK_NE(player, players_.end()); - player->value->messages.AppendRange(messages.begin(), messages.end()); + if (player != players_.end()) { + player->value->messages.AppendRange(messages.begin(), messages.end()); + total_event_count_ += messages.size(); + if (total_event_count_ > kMaxCachedPlayerEvents) + CullPlayers(playerId); + } Vector<InspectorPlayerMessage> vector = Iter2Vector<InspectorPlayerMessage>(messages); probe::PlayerMessagesLogged(GetSupplementable(), playerId, vector); } +HeapHashMap<String, Member<MediaPlayer>>* +MediaInspectorContextImpl::GetPlayersForTesting() { + return &players_; +} + } // namespace blink
diff --git a/third_party/blink/renderer/core/inspector/inspector_media_context_impl.h b/third_party/blink/renderer/core/inspector/inspector_media_context_impl.h index ff5b740..6fe4613 100644 --- a/third_party/blink/renderer/core/inspector/inspector_media_context_impl.h +++ b/third_party/blink/renderer/core/inspector/inspector_media_context_impl.h
@@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_INSPECTOR_MEDIA_CONTEXT_IMPL_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_INSPECTOR_MEDIA_CONTEXT_IMPL_H_ +#include "build/build_config.h" #include "third_party/blink/public/web/web_media_inspector.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/platform/supplementable.h" @@ -13,6 +14,13 @@ namespace blink { +#if defined(OS_ANDROID) +// Players are cached per tab. +constexpr int kMaxCachedPlayerEvents = 128; +#else +constexpr int kMaxCachedPlayerEvents = 512; +#endif + class ExecutionContext; struct MediaPlayer final : public GarbageCollected<MediaPlayer> { @@ -38,6 +46,8 @@ // MediaInspectorContext methods. WebString CreatePlayer() override; + void DestroyPlayer(const WebString& playerId) override; + void NotifyPlayerErrors(WebString playerId, const InspectorPlayerErrors&) override; void NotifyPlayerEvents(WebString playerId, @@ -50,11 +60,37 @@ // GarbageCollected methods. void Trace(Visitor*) const override; - Vector<WebString> AllPlayerIds(); + Vector<WebString> AllPlayerIdsAndMarkSent(); const MediaPlayer& MediaPlayerFromId(const WebString&); + HeapHashMap<String, Member<MediaPlayer>>* GetPlayersForTesting(); + int GetTotalEventCountForTesting() { return total_event_count_; } + private: + // When a player is added, its ID is stored in |unsent_players_| if no + // connections are open. When an unsent player is destroyed, its ID is moved + // to |dead_players_| and is first to be deleted if there is memory pressure. + // If it has already been sent when it is destroyed, it gets moved to + // |expendable_players_|, which is the second group of players to be deleted + // on memory pressure. + + // If there are no dead or expendable players when it's time to start removing + // players, then a player from |unsent_players_| will be removed. As a last + // resort, remaining unended, already-sent players will be removed from + // |players_| until the total event size is within the limit. + + // All events will be sent to any open clients regardless of players existing + // because the clients can handle dead players and may have their own cache. + void CullPlayers(const WebString& prefer_keep); + void TrimPlayer(const WebString& playerId); + void RemovePlayer(const WebString& playerId); + HeapHashMap<String, Member<MediaPlayer>> players_; + Vector<String> unsent_players_; + Vector<String> dead_players_; + Vector<String> expendable_players_; + + int total_event_count_ = 0; }; } // namespace blink
diff --git a/third_party/blink/renderer/core/inspector/inspector_media_context_impl_unittest.cc b/third_party/blink/renderer/core/inspector/inspector_media_context_impl_unittest.cc new file mode 100644 index 0000000..e23ca6b0 --- /dev/null +++ b/third_party/blink/renderer/core/inspector/inspector_media_context_impl_unittest.cc
@@ -0,0 +1,143 @@ +// 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. + +#include "third_party/blink/renderer/core/inspector/inspector_media_context_impl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" +#include "third_party/blink/renderer/core/testing/null_execution_context.h" + +namespace blink { +namespace { + +class InspectorMediaContextImplTest : public ::testing::Test { + protected: + void SetUp() override { + dummy_page_holder_ = + std::make_unique<DummyPageHolder>(IntSize(), nullptr, nullptr); + impl = MediaInspectorContextImpl::From( + *dummy_page_holder_->GetFrame().DomWindow()); + } + + InspectorPlayerEvents MakeEvents(size_t ev_count) { + InspectorPlayerEvents to_add; + while (ev_count-- > 0) { + blink::InspectorPlayerEvent ev = {base::TimeTicks::Now(), "foo"}; + to_add.emplace_back(std::move(ev)); + } + return to_add; + } + + Persistent<MediaInspectorContextImpl> impl; + std::unique_ptr<DummyPageHolder> dummy_page_holder_; +}; + +TEST_F(InspectorMediaContextImplTest, CanCreatePlayerAndAddEvents) { + auto id = impl->CreatePlayer(); + auto* players = impl->GetPlayersForTesting(); + EXPECT_EQ(players->size(), 1u); + EXPECT_TRUE(players->at(id)->errors.IsEmpty()); + EXPECT_TRUE(players->at(id)->events.IsEmpty()); + EXPECT_TRUE(players->at(id)->messages.IsEmpty()); + EXPECT_TRUE(players->at(id)->properties.IsEmpty()); + + impl->NotifyPlayerEvents(id, MakeEvents(10)); + EXPECT_EQ(players->at(id)->events.size(), wtf_size_t{10}); +} + +TEST_F(InspectorMediaContextImplTest, KillsPlayersInCorrectOrder) { + auto alive_player_id = impl->CreatePlayer(); + auto expendable_player_id = impl->CreatePlayer(); + // Also marks the alive / expendable players as sent. + ASSERT_EQ(impl->AllPlayerIdsAndMarkSent().size(), wtf_size_t{2}); + + // These are created, but unsent. + auto dead_player_id = impl->CreatePlayer(); + auto unsent_player_id = impl->CreatePlayer(); + + // check that there are 4. + EXPECT_EQ(impl->GetPlayersForTesting()->size(), wtf_size_t{4}); + + // mark these as dead to get them into their respective states. + impl->DestroyPlayer(dead_player_id); + impl->DestroyPlayer(expendable_player_id); + + // check that there are still 4. + EXPECT_EQ(impl->GetPlayersForTesting()->size(), wtf_size_t{4}); + + // Almost fill up the total cache size. + impl->NotifyPlayerEvents(dead_player_id, MakeEvents(10)); + impl->NotifyPlayerEvents(unsent_player_id, MakeEvents(10)); + impl->NotifyPlayerEvents(expendable_player_id, MakeEvents(10)); + impl->NotifyPlayerEvents(alive_player_id, + MakeEvents(kMaxCachedPlayerEvents - 32)); + + EXPECT_EQ(impl->GetTotalEventCountForTesting(), kMaxCachedPlayerEvents - 2); + EXPECT_EQ(impl->GetPlayersForTesting()->size(), wtf_size_t{4}); + + // If we keep adding events to the alive player in groups of 10, it should + // delete the other players in the order: dead, expendable, unsent. + impl->NotifyPlayerEvents(alive_player_id, MakeEvents(10)); + + // The number of events remains unchanged, players at 3, and no dead id. + EXPECT_EQ(impl->GetTotalEventCountForTesting(), kMaxCachedPlayerEvents - 2); + EXPECT_EQ(impl->GetPlayersForTesting()->size(), wtf_size_t{3}); + EXPECT_FALSE(impl->GetPlayersForTesting()->Contains(dead_player_id)); + + // Kill the expendable player. + impl->NotifyPlayerEvents(alive_player_id, MakeEvents(10)); + + // The number of events remains unchanged, players at 2, and no expendable id. + EXPECT_EQ(impl->GetTotalEventCountForTesting(), kMaxCachedPlayerEvents - 2); + EXPECT_EQ(impl->GetPlayersForTesting()->size(), wtf_size_t{2}); + EXPECT_FALSE(impl->GetPlayersForTesting()->Contains(expendable_player_id)); + + // Kill the unsent player. + impl->NotifyPlayerEvents(alive_player_id, MakeEvents(10)); + + // The number of events remains unchanged, players at 1, and no unsent id. + EXPECT_EQ(impl->GetTotalEventCountForTesting(), kMaxCachedPlayerEvents - 2); + EXPECT_EQ(impl->GetPlayersForTesting()->size(), wtf_size_t{1}); + EXPECT_FALSE(impl->GetPlayersForTesting()->Contains(unsent_player_id)); + + // Overflow the the cache and start trimming events. + impl->NotifyPlayerEvents(alive_player_id, MakeEvents(10)); + + // The number of events remains unchanged, players at 1, and no unsent id. + EXPECT_EQ(impl->GetTotalEventCountForTesting(), kMaxCachedPlayerEvents); + EXPECT_EQ(impl->GetPlayersForTesting()->size(), wtf_size_t{1}); + EXPECT_TRUE(impl->GetPlayersForTesting()->Contains(alive_player_id)); +} + +TEST_F(InspectorMediaContextImplTest, OkToSendForDeadPlayers) { + auto player_1 = impl->CreatePlayer(); + auto player_2 = impl->CreatePlayer(); + ASSERT_EQ(impl->AllPlayerIdsAndMarkSent().size(), wtf_size_t{2}); + + // This should evict player1. + impl->NotifyPlayerEvents(player_1, MakeEvents(kMaxCachedPlayerEvents - 1)); + impl->NotifyPlayerEvents(player_2, MakeEvents(10)); + EXPECT_EQ(impl->GetPlayersForTesting()->size(), wtf_size_t{1}); + EXPECT_FALSE(impl->GetPlayersForTesting()->Contains(player_1)); + + // Sending events to an evicted player shouldn't cause the cache size to + // increase, or any new evictions to happen. + impl->NotifyPlayerEvents(player_1, MakeEvents(kMaxCachedPlayerEvents - 1)); + EXPECT_EQ(impl->GetPlayersForTesting()->size(), wtf_size_t{1}); + EXPECT_FALSE(impl->GetPlayersForTesting()->Contains(player_1)); +} + +TEST_F(InspectorMediaContextImplTest, TrimLastRemainingPlayer) { + auto player_1 = impl->CreatePlayer(); + ASSERT_EQ(impl->AllPlayerIdsAndMarkSent().size(), wtf_size_t{1}); + + impl->NotifyPlayerEvents(player_1, MakeEvents(kMaxCachedPlayerEvents - 1)); + impl->NotifyPlayerEvents(player_1, MakeEvents(kMaxCachedPlayerEvents - 1)); + EXPECT_EQ(impl->GetPlayersForTesting()->size(), wtf_size_t{1}); + EXPECT_TRUE(impl->GetPlayersForTesting()->Contains(player_1)); + EXPECT_EQ(impl->GetTotalEventCountForTesting(), kMaxCachedPlayerEvents); +} + +} // namespace +} // namespace blink
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc index 9d8847c..a588a2e 100644 --- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc +++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
@@ -719,6 +719,7 @@ const char kChildChanged[] = "Child changed"; const char kListValueChange[] = "List value change"; const char kListStyleTypeChange[] = "List style type change"; +const char kCounterStyleChange[] = "Counter style change"; const char kImageChanged[] = "Image changed"; const char kLineBoxesChanged[] = "Line boxes changed"; const char kSliderValueChanged[] = "Slider value changed";
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.h b/third_party/blink/renderer/core/inspector/inspector_trace_events.h index 757e45a..a9b75306 100644 --- a/third_party/blink/renderer/core/inspector/inspector_trace_events.h +++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.h
@@ -240,6 +240,7 @@ extern const char kChildChanged[]; extern const char kListValueChange[]; extern const char kListStyleTypeChange[]; +extern const char kCounterStyleChange[]; extern const char kImageChanged[]; extern const char kLineBoxesChanged[]; extern const char kSliderValueChanged[];
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc index 6c47f590..ccab445 100644 --- a/third_party/blink/renderer/core/layout/layout_box.cc +++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -336,9 +336,12 @@ if (new_fragment.HasItems()) DCHECK(box.ChildrenInline()); + wtf_size_t index = 0; for (const NGPhysicalBoxFragment& fragment : box.PhysicalFragments()) { + DCHECK_EQ(fragment.IsFirstForNode(), index == 0); if (const NGFragmentItems* fragment_items = fragment.Items()) fragment_items->CheckAllItemsAreValid(); + ++index; } } #else @@ -3160,6 +3163,22 @@ return false; } +wtf_size_t LayoutBox::NGPhysicalFragmentList::IndexOf( + const NGPhysicalBoxFragment& fragment) const { + wtf_size_t index = 0; + for (const scoped_refptr<const NGLayoutResult>& result : layout_results_) { + if (&result->PhysicalFragment() == &fragment) + return index; + ++index; + } + return kNotFound; +} + +bool LayoutBox::NGPhysicalFragmentList::Contains( + const NGPhysicalBoxFragment& fragment) const { + return IndexOf(fragment) != kNotFound; +} + void LayoutBox::SetCachedLayoutResult( scoped_refptr<const NGLayoutResult> result) { NOT_DESTROYED(); @@ -3198,7 +3217,11 @@ return; } - DCHECK_EQ(index, layout_results_.size()); + DCHECK(index == layout_results_.size() || index == kNotFound); + AddLayoutResult(std::move(result)); +} + +void LayoutBox::AddLayoutResult(scoped_refptr<const NGLayoutResult> result) { const auto& fragment = To<NGPhysicalBoxFragment>(result->PhysicalFragment()); layout_results_.push_back(std::move(result)); CheckDidAddFragment(*this, fragment); @@ -3244,6 +3267,21 @@ NGFragmentItems::FinalizeAfterLayout(layout_results_); } +void LayoutBox::ReplaceLayoutResult(scoped_refptr<const NGLayoutResult> result, + const NGPhysicalBoxFragment& old_fragment) { + DCHECK_EQ(this, old_fragment.OwnerLayoutBox()); + DCHECK_EQ(result->PhysicalFragment().GetSelfOrContainerLayoutObject(), + old_fragment.GetSelfOrContainerLayoutObject()); + // TODO(kojii): |IndexOf| is O(n). Consider if we can avoid this. + const wtf_size_t index = PhysicalFragments().IndexOf(old_fragment); + if (index != kNotFound) { + ReplaceLayoutResult(std::move(result), index); + return; + } + NOTREACHED(); + AddLayoutResult(std::move(result)); +} + void LayoutBox::ClearLayoutResults() { NOT_DESTROYED(); if (measure_result_)
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h index 2f7a56d..b7bdc87 100644 --- a/third_party/blink/renderer/core/layout/layout_box.h +++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -1183,8 +1183,11 @@ // Store one layout result (with its physical fragment) at the specified // index, and delete all entries following it. void AddLayoutResult(scoped_refptr<const NGLayoutResult>, wtf_size_t index); + void AddLayoutResult(scoped_refptr<const NGLayoutResult>); void ReplaceLayoutResult(scoped_refptr<const NGLayoutResult>, wtf_size_t index); + void ReplaceLayoutResult(scoped_refptr<const NGLayoutResult>, + const NGPhysicalBoxFragment& old_fragment); void ShrinkLayoutResults(wtf_size_t results_to_keep); void ClearLayoutResults(); @@ -1222,6 +1225,9 @@ bool HasFragmentItems() const; + wtf_size_t IndexOf(const NGPhysicalBoxFragment& fragment) const; + bool Contains(const NGPhysicalBoxFragment& fragment) const; + class CORE_EXPORT Iterator : public std::iterator<std::forward_iterator_tag, NGPhysicalBoxFragment> { public:
diff --git a/third_party/blink/renderer/core/layout/layout_counter.cc b/third_party/blink/renderer/core/layout/layout_counter.cc index 67164bb..e3738559 100644 --- a/third_party/blink/renderer/core/layout/layout_counter.cc +++ b/third_party/blink/renderer/core/layout/layout_counter.cc
@@ -674,7 +674,7 @@ maps.erase(maps_iterator); owner.SetHasCounterNodeMap(false); if (owner.View()) - owner.View()->SetNeedsCounterUpdate(); + owner.View()->SetNeedsMarkerOrCounterUpdate(); } void LayoutCounter::DestroyCounterNode(LayoutObject& owner,
diff --git a/third_party/blink/renderer/core/layout/layout_list_item.cc b/third_party/blink/renderer/core/layout/layout_list_item.cc index 027b7ef..b8bb7ecc 100644 --- a/third_party/blink/renderer/core/layout/layout_list_item.cc +++ b/third_party/blink/renderer/core/layout/layout_list_item.cc
@@ -28,6 +28,7 @@ #include "third_party/blink/renderer/core/html/html_olist_element.h" #include "third_party/blink/renderer/core/layout/layout_list_marker.h" #include "third_party/blink/renderer/core/layout/layout_outside_list_marker.h" +#include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/list_marker.h" #include "third_party/blink/renderer/core/paint/list_item_painter.h" #include "third_party/blink/renderer/core/paint/paint_layer.h" @@ -43,6 +44,14 @@ SetConsumesSubtreeChangeNotification(); RegisterSubtreeChangeListenerOnDescendants(true); + View()->AddLayoutListItem(); +} + +void LayoutListItem::WillBeDestroyed() { + NOT_DESTROYED(); + if (View()) + View()->RemoveLayoutListItem(); + LayoutBlockFlow::WillBeDestroyed(); } void LayoutListItem::StyleDidChange(StyleDifference diff, @@ -85,6 +94,27 @@ } } +void LayoutListItem::UpdateCounterStyle() { + NOT_DESTROYED(); + + if (!StyleRef().GetListStyleType() || + StyleRef().GetListStyleType()->IsCounterStyleReferenceValid( + GetDocument())) { + return; + } + + LayoutObject* marker = Marker(); + if (!marker) + return; + + if (auto* legacy_marker = DynamicTo<LayoutListMarker>(marker)) { + legacy_marker->CounterStyleChanged(); + return; + } + + ListMarker::Get(marker)->CounterStyleChanged(*marker); +} + void LayoutListItem::InsertedIntoTree() { NOT_DESTROYED(); LayoutBlockFlow::InsertedIntoTree();
diff --git a/third_party/blink/renderer/core/layout/layout_list_item.h b/third_party/blink/renderer/core/layout/layout_list_item.h index a7fdeeaa..d5e56c3 100644 --- a/third_party/blink/renderer/core/layout/layout_list_item.h +++ b/third_party/blink/renderer/core/layout/layout_list_item.h
@@ -58,6 +58,8 @@ void UpdateMarkerTextIfNeeded(); + void UpdateCounterStyle(); + private: bool IsOfType(LayoutObjectType type) const override { NOT_DESTROYED(); @@ -82,6 +84,8 @@ void AddLayoutOverflowFromChildren() override; + void WillBeDestroyed() override; + void AlignMarkerInBlockDirection(); bool PrepareForBlockDirectionAlign(const LayoutObject*);
diff --git a/third_party/blink/renderer/core/layout/layout_list_marker.cc b/third_party/blink/renderer/core/layout/layout_list_marker.cc index e226c75..4cbdc40 100644 --- a/third_party/blink/renderer/core/layout/layout_list_marker.cc +++ b/third_party/blink/renderer/core/layout/layout_list_marker.cc
@@ -87,6 +87,14 @@ layout_invalidation_reason::kListStyleTypeChange); } +void LayoutListMarker::CounterStyleChanged() { + NOT_DESTROYED(); + if (IsImage()) + return; + SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( + layout_invalidation_reason::kCounterStyleChange); +} + void LayoutListMarker::UpdateMarkerImageIfNeeded(StyleImage* image) { NOT_DESTROYED(); if (image_ != image) { @@ -346,8 +354,7 @@ const ListStyleTypeData* list_style_data = StyleRef().GetListStyleType(); DCHECK(list_style_data); DCHECK(list_style_data->IsCounterStyle()); - return GetDocument().GetStyleEngine().FindCounterStyleAcrossScopes( - list_style_data->GetCounterStyleName(), list_style_data->GetTreeScope()); + return list_style_data->GetCounterStyle(GetDocument()); } bool LayoutListMarker::IsInside() const {
diff --git a/third_party/blink/renderer/core/layout/layout_list_marker.h b/third_party/blink/renderer/core/layout/layout_list_marker.h index 5104a2b..6042c09 100644 --- a/third_party/blink/renderer/core/layout/layout_list_marker.h +++ b/third_party/blink/renderer/core/layout/layout_list_marker.h
@@ -117,6 +117,7 @@ void UpdateMarkerImageIfNeeded(StyleImage* image); void ListStyleTypeChanged(); + void CounterStyleChanged(); String text_; Persistent<StyleImage> image_;
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc index 96153f3..f446fdc 100644 --- a/third_party/blink/renderer/core/layout/layout_object.cc +++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -3027,7 +3027,7 @@ return; } LayoutCounter::LayoutObjectStyleChanged(*this, old_style, *new_style); - View()->SetNeedsCounterUpdate(); + View()->SetNeedsMarkerOrCounterUpdate(); } PhysicalRect LayoutObject::ViewRect() const {
diff --git a/third_party/blink/renderer/core/layout/layout_view.cc b/third_party/blink/renderer/core/layout/layout_view.cc index c21e406..74d67fa 100644 --- a/third_party/blink/renderer/core/layout/layout_view.cc +++ b/third_party/blink/renderer/core/layout/layout_view.cc
@@ -43,6 +43,8 @@ #include "third_party/blink/renderer/core/layout/layout_counter.h" #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" #include "third_party/blink/renderer/core/layout/layout_geometry_map.h" +#include "third_party/blink/renderer/core/layout/layout_list_item.h" +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h" #include "third_party/blink/renderer/core/layout/view_fragmentation_context.h" #include "third_party/blink/renderer/core/page/chrome_client.h" @@ -956,19 +958,25 @@ return CompositingReason::kNone; } -void LayoutView::UpdateCounters() { +void LayoutView::UpdateMarkersAndCountersAfterStyleChange() { NOT_DESTROYED(); - if (!needs_counter_update_) + if (!needs_marker_counter_update_) return; - needs_counter_update_ = false; - if (!HasLayoutCounters()) + needs_marker_counter_update_ = false; + if (!HasLayoutCounters() && !HasLayoutListItems()) return; for (LayoutObject* layout_object = this; layout_object; layout_object = layout_object->NextInPreOrder()) { - if (auto* counter = DynamicTo<LayoutCounter>(layout_object)) + if (auto* list_item = DynamicTo<LayoutListItem>(layout_object)) { + list_item->UpdateCounterStyle(); + } else if (auto* ng_list_item = + DynamicTo<LayoutNGListItem>(layout_object)) { + ng_list_item->UpdateCounterStyle(); + } else if (auto* counter = DynamicTo<LayoutCounter>(layout_object)) { counter->UpdateCounter(); + } } }
diff --git a/third_party/blink/renderer/core/layout/layout_view.h b/third_party/blink/renderer/core/layout/layout_view.h index 51d607f..a8b21ba 100644 --- a/third_party/blink/renderer/core/layout/layout_view.h +++ b/third_party/blink/renderer/core/layout/layout_view.h
@@ -246,11 +246,12 @@ // FIXME: This is a work around because the current implementation of counters // requires walking the entire tree repeatedly and most pages don't actually // use either feature so we shouldn't take the performance hit when not - // needed. Long term we should rewrite the counter and quotes code. + // needed. Long term we should rewrite the counter code. + // TODO(xiaochengh): Or do we keep it as is? void AddLayoutCounter() { NOT_DESTROYED(); layout_counter_count_++; - SetNeedsCounterUpdate(); + SetNeedsMarkerOrCounterUpdate(); } void RemoveLayoutCounter() { NOT_DESTROYED(); @@ -261,11 +262,25 @@ NOT_DESTROYED(); return layout_counter_count_; } - void SetNeedsCounterUpdate() { + void AddLayoutListItem() { NOT_DESTROYED(); - needs_counter_update_ = true; + layout_list_item_count_++; + SetNeedsMarkerOrCounterUpdate(); } - void UpdateCounters(); + void RemoveLayoutListItem() { + NOT_DESTROYED(); + DCHECK_GT(layout_list_item_count_, 0u); + layout_list_item_count_--; + } + bool HasLayoutListItems() { + NOT_DESTROYED(); + return layout_list_item_count_; + } + void SetNeedsMarkerOrCounterUpdate() { + NOT_DESTROYED(); + needs_marker_counter_update_ = true; + } + void UpdateMarkersAndCountersAfterStyleChange(); bool BackgroundIsKnownToBeOpaqueInRect( const PhysicalRect& local_rect) const override; @@ -378,8 +393,9 @@ scoped_refptr<IntervalArena> interval_arena_; LayoutQuote* layout_quote_head_; - unsigned layout_counter_count_; - bool needs_counter_update_ = false; + unsigned layout_counter_count_ = 0; + unsigned layout_list_item_count_ = 0; + bool needs_marker_counter_update_ = false; unsigned hit_test_count_; unsigned hit_test_cache_hits_;
diff --git a/third_party/blink/renderer/core/layout/list_marker.cc b/third_party/blink/renderer/core/layout/list_marker.cc index 7ab6db2a..c1c7be0d 100644 --- a/third_party/blink/renderer/core/layout/list_marker.cc +++ b/third_party/blink/renderer/core/layout/list_marker.cc
@@ -93,8 +93,7 @@ return 0; } -// If the value of ListStyleType changed, we need to the marker text has been -// updated. +// If the value of ListStyleType changed, we need to update the marker text. void ListMarker::ListStyleTypeChanged(LayoutObject& marker) { DCHECK_EQ(Get(&marker), this); if (marker_text_type_ == kNotText || marker_text_type_ == kUnresolved) @@ -105,6 +104,17 @@ layout_invalidation_reason::kListStyleTypeChange); } +// If the @counter-style in use has changed, we need to update the marker text. +void ListMarker::CounterStyleChanged(LayoutObject& marker) { + DCHECK_EQ(Get(&marker), this); + if (marker_text_type_ == kNotText || marker_text_type_ == kUnresolved) + return; + + marker_text_type_ = kUnresolved; + marker.SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( + layout_invalidation_reason::kCounterStyleChange); +} + void ListMarker::OrdinalValueChanged(LayoutObject& marker) { DCHECK_EQ(Get(&marker), this); if (marker_text_type_ == kOrdinalValue) { @@ -420,9 +430,7 @@ DCHECK(RuntimeEnabledFeatures::CSSAtRuleCounterStyleEnabled()); DCHECK(style.GetListStyleType()); DCHECK(style.GetListStyleType()->IsCounterStyle()); - const ListStyleTypeData& list_style_data = *style.GetListStyleType(); - return document.GetStyleEngine().FindCounterStyleAcrossScopes( - list_style_data.GetCounterStyleName(), list_style_data.GetTreeScope()); + return style.GetListStyleType()->GetCounterStyle(document); } ListMarker::ListStyleCategory ListMarker::GetListStyleCategory(
diff --git a/third_party/blink/renderer/core/layout/list_marker.h b/third_party/blink/renderer/core/layout/list_marker.h index 1f3fd85..e9ed1b3 100644 --- a/third_party/blink/renderer/core/layout/list_marker.h +++ b/third_party/blink/renderer/core/layout/list_marker.h
@@ -94,6 +94,7 @@ void ListStyleTypeChanged(LayoutObject&); void OrdinalValueChanged(LayoutObject&); + void CounterStyleChanged(LayoutObject&); int ListItemValue(const LayoutObject&) const;
diff --git a/third_party/blink/renderer/core/layout/list_marker_test.cc b/third_party/blink/renderer/core/layout/list_marker_test.cc new file mode 100644 index 0000000..aa70c9a --- /dev/null +++ b/third_party/blink/renderer/core/layout/list_marker_test.cc
@@ -0,0 +1,312 @@ +// 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 "third_party/blink/renderer/core/layout/list_marker.h" + +#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" +#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h" + +namespace blink { + +// We don't test legacy layout because it's deprecated, and we don't want to +// complicate the test with the legacy LayoutListMarker here. +class ListMarkerTest : public NGLayoutTest { + protected: + LayoutObject* GetMarker(const char* list_item_id) { + LayoutNGListItem* list_item = + To<LayoutNGListItem>(GetLayoutObjectByElementId(list_item_id)); + return list_item->Marker(); + } + + LayoutObject* GetMarker(TreeScope& scope, const char* list_item_id) { + Element* list_item = scope.getElementById(list_item_id); + return To<LayoutNGListItem>(list_item->GetLayoutObject())->Marker(); + } + + String GetMarkerText(TreeScope& scope, const char* list_item_id) { + return To<LayoutText>(GetMarker(scope, list_item_id)->SlowFirstChild()) + ->GetText(); + } + + String GetMarkerText(const char* list_item_id) { + return GetMarkerText(GetDocument(), list_item_id); + } + + void AddCounterStyle(const AtomicString& name, const String& descriptors) { + StringBuilder declaration; + declaration.Append("@counter-style "); + declaration.Append(name); + declaration.Append("{"); + declaration.Append(descriptors); + declaration.Append("}"); + Element* sheet = GetDocument().CreateElementForBinding("style"); + sheet->setInnerHTML(declaration.ToString()); + GetDocument().body()->appendChild(sheet); + } +}; + +TEST_F(ListMarkerTest, AddCounterStyle) { + GetDocument().body()->setInnerHTML(R"HTML( + <style> + @counter-style foo { + system: fixed; + symbols: W X Y Z; + } + </style> + <ol> + <li id="decimal" style="list-style-type: decimal"></li> + <li id="foo" style="list-style-type: foo"></li> + <li id="bar" style="list-style-type: bar"></li> + </ol> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("X. ", GetMarkerText("foo")); + EXPECT_EQ("3. ", GetMarkerText("bar")); + + // Add @counter-style 'bar'. Should not affect 'decimal' and 'foo'. + AddCounterStyle("bar", "system: fixed; symbols: A B C;"); + GetDocument().UpdateStyleAndLayoutTree(); + + EXPECT_FALSE(GetMarker("decimal")->NeedsLayout()); + EXPECT_FALSE(GetMarker("foo")->NeedsLayout()); + EXPECT_TRUE(GetMarker("bar")->NeedsLayout()); + + UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("X. ", GetMarkerText("foo")); + EXPECT_EQ("C. ", GetMarkerText("bar")); +} + +TEST_F(ListMarkerTest, RemoveCounterStyle) { + GetDocument().body()->setInnerHTML(R"HTML( + <style id="foo-sheet"> + @counter-style foo { + system: fixed; + symbols: W X Y Z; + } + </style> + <ol> + <li id="decimal" style="list-style-type: decimal"></li> + <li id="foo" style="list-style-type: foo"></li> + </ol> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("X. ", GetMarkerText("foo")); + + // Remove @counter-style 'foo'. Should not affect 'decimal'. + GetElementById("foo-sheet")->remove(); + GetDocument().UpdateStyleAndLayoutTree(); + + EXPECT_FALSE(GetMarker("decimal")->NeedsLayout()); + EXPECT_TRUE(GetMarker("foo")->NeedsLayout()); + + UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("2. ", GetMarkerText("foo")); +} + +TEST_F(ListMarkerTest, OverridePredefinedCounterStyle) { + GetDocument().body()->setInnerHTML(R"HTML( + <ol> + <li id="decimal" style="list-style-type: decimal"></li> + <li id="upper-roman" style="list-style-type: upper-roman"></li> + </ol> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("II. ", GetMarkerText("upper-roman")); + + // Override 'upper-roman'. Should not affect 'decimal'. + AddCounterStyle("upper-roman", "system: fixed; symbols: A B C;"); + GetDocument().UpdateStyleAndLayoutTree(); + + EXPECT_FALSE(GetMarker("decimal")->NeedsLayout()); + EXPECT_TRUE(GetMarker("upper-roman")->NeedsLayout()); + + UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("B. ", GetMarkerText("upper-roman")); +} + +TEST_F(ListMarkerTest, RemoveOverrideOfPredefinedCounterStyle) { + GetDocument().body()->setInnerHTML(R"HTML( + <style id="to-remove"> + @counter-style upper-roman { + system: fixed; + symbols: A B C; + } + </style> + <ol> + <li id="decimal" style="list-style-type: decimal"></li> + <li id="upper-roman" style="list-style-type: upper-roman"></li> + </ol> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("B. ", GetMarkerText("upper-roman")); + + // Remove override of 'upper-roman'. Should not affect 'decimal'. + GetElementById("to-remove")->remove(); + GetDocument().UpdateStyleAndLayoutTree(); + + EXPECT_FALSE(GetMarker("decimal")->NeedsLayout()); + EXPECT_TRUE(GetMarker("upper-roman")->NeedsLayout()); + + UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("II. ", GetMarkerText("upper-roman")); +} + +TEST_F(ListMarkerTest, OverrideSameScopeCounterStyle) { + GetDocument().body()->setInnerHTML(R"HTML( + <style> + @counter-style foo { + system: fixed; + symbols: W X Y Z; + } + </style> + <ol> + <li id="decimal" style="list-style-type: decimal"></li> + <li id="foo" style="list-style-type: foo"></li> + </ol> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("X. ", GetMarkerText("foo")); + + // Override 'foo'. Should not affect 'decimal'. + AddCounterStyle("foo", "system: fixed; symbols: A B C;"); + GetDocument().UpdateStyleAndLayoutTree(); + + EXPECT_FALSE(GetMarker("decimal")->NeedsLayout()); + EXPECT_TRUE(GetMarker("foo")->NeedsLayout()); + + UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("B. ", GetMarkerText("foo")); +} + +TEST_F(ListMarkerTest, RemoveOverrideOfSameScopeCounterStyle) { + GetDocument().body()->setInnerHTML(R"HTML( + <style> + @counter-style foo { + system: fixed; + symbols: W X Y Z; + } + </style> + <style id="to-remove"> + @counter-style foo { + system: fixed; + symbols: A B C; + } + </style> + <ol> + <li id="decimal" style="list-style-type: decimal"></li> + <li id="foo" style="list-style-type: foo"></li> + </ol> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("B. ", GetMarkerText("foo")); + + // Remove the override of 'foo'. Should not affect 'decimal'. + GetElementById("to-remove")->remove(); + GetDocument().UpdateStyleAndLayoutTree(); + + EXPECT_FALSE(GetMarker("decimal")->NeedsLayout()); + EXPECT_TRUE(GetMarker("foo")->NeedsLayout()); + + UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("X. ", GetMarkerText("foo")); +} + +TEST_F(ListMarkerTest, ModifyShadowDOMWithOwnCounterStyles) { + GetDocument().body()->setInnerHTML(R"HTML( + <style> + @counter-style foo { + system: fixed; + symbols: W X Y Z; + } + </style> + <ol> + <li id="decimal" style="list-style-type: decimal"></li> + <li id="foo" style="list-style-type: foo"></li> + </ol> + <div id="host1"></div> + <div id="host2"></div> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("X. ", GetMarkerText("foo")); + + // Attach a shadow tree with counter styles. Shouldn't affect anything outside + ShadowRoot& shadow1 = + GetElementById("host1")->AttachShadowRootInternal(ShadowRootType::kOpen); + shadow1.setInnerHTML(R"HTML( + <style> + @counter-style foo { + system: fixed; + symbols: A B C; + } + </style> + <ol> + <li id="shadow-foo" style="list-style-type: foo"></li> + </ol> + )HTML"); + GetDocument().UpdateStyleAndLayoutTree(); + EXPECT_FALSE(GetMarker("decimal")->NeedsLayout()); + EXPECT_FALSE(GetMarker("foo")->NeedsLayout()); + UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("X. ", GetMarkerText("foo")); + EXPECT_EQ("A. ", GetMarkerText(shadow1, "shadow-foo")); + + // Attach another shadow tree with counter styles. Shouldn't affect anything + // outside. + ShadowRoot& shadow2 = + GetElementById("host2")->AttachShadowRootInternal(ShadowRootType::kOpen); + shadow2.setInnerHTML(R"HTML( + <style> + @counter-style foo { + system: fixed; + symbols: D E F; + } + </style> + <ol> + <li id="shadow-foo" style="list-style-type: foo"></li> + </ol> + )HTML"); + GetDocument().UpdateStyleAndLayoutTree(); + EXPECT_FALSE(GetMarker("decimal")->NeedsLayout()); + EXPECT_FALSE(GetMarker("foo")->NeedsLayout()); + EXPECT_FALSE(GetMarker(shadow1, "shadow-foo")->NeedsLayout()); + UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("X. ", GetMarkerText("foo")); + EXPECT_EQ("A. ", GetMarkerText(shadow1, "shadow-foo")); + EXPECT_EQ("D. ", GetMarkerText(shadow2, "shadow-foo")); + + // Remove one of the shadow trees. Shouldn't affect anything outside. + GetElementById("host1")->remove(); + GetDocument().UpdateStyleAndLayoutTree(); + EXPECT_FALSE(GetMarker("decimal")->NeedsLayout()); + EXPECT_FALSE(GetMarker("foo")->NeedsLayout()); + EXPECT_FALSE(GetMarker(shadow2, "shadow-foo")->NeedsLayout()); + UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ("1. ", GetMarkerText("decimal")); + EXPECT_EQ("X. ", GetMarkerText("foo")); + EXPECT_EQ("D. ", GetMarkerText(shadow2, "shadow-foo")); +} + +} // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc index ff8111a..60eadd0e 100644 --- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc +++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.cc
@@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h" +#include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/list_marker.h" namespace blink { @@ -14,6 +15,14 @@ SetConsumesSubtreeChangeNotification(); RegisterSubtreeChangeListenerOnDescendants(true); + View()->AddLayoutListItem(); +} + +void LayoutNGListItem::WillBeDestroyed() { + NOT_DESTROYED(); + if (View()) + View()->RemoveLayoutListItem(); + LayoutNGBlockFlow::WillBeDestroyed(); } bool LayoutNGListItem::IsOfType(LayoutObjectType type) const { @@ -55,6 +64,21 @@ } } +void LayoutNGListItem::UpdateCounterStyle() { + if (!StyleRef().GetListStyleType() || + StyleRef().GetListStyleType()->IsCounterStyleReferenceValid( + GetDocument())) { + return; + } + + LayoutObject* marker = Marker(); + ListMarker* list_marker = ListMarker::Get(marker); + if (!list_marker) + return; + + list_marker->CounterStyleChanged(*marker); +} + void LayoutNGListItem::OrdinalValueChanged() { LayoutObject* marker = Marker(); if (ListMarker* list_marker = ListMarker::Get(marker))
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h index 02ddf45e..ebef26d1 100644 --- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h +++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h
@@ -26,6 +26,7 @@ } void UpdateMarkerTextIfNeeded(); + void UpdateCounterStyle(); void OrdinalValueChanged(); void WillCollectInlines() override; @@ -41,6 +42,7 @@ void WillBeRemovedFromTree() override; void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override; void SubtreeDidChange() final; + void WillBeDestroyed() override; ListItemOrdinal ordinal_; };
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 351ff7c7..1f7e9623 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
@@ -1766,4 +1766,16 @@ GetFlowThread(To<LayoutBlockFlow>(box_))->AddLayoutResult(result, index); } +void NGBlockNode::AddColumnResult( + scoped_refptr<const NGLayoutResult> result) const { + GetFlowThread(To<LayoutBlockFlow>(box_))->AddLayoutResult(std::move(result)); +} + +void NGBlockNode::ReplaceColumnResult( + scoped_refptr<const NGLayoutResult> result, + const NGPhysicalBoxFragment& old_fragment) const { + GetFlowThread(To<LayoutBlockFlow>(box_)) + ->ReplaceLayoutResult(std::move(result), old_fragment); +} + } // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.h b/third_party/blink/renderer/core/layout/ng/ng_block_node.h index dd62a12..e16f9e3 100644 --- a/third_party/blink/renderer/core/layout/ng/ng_block_node.h +++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.h
@@ -176,6 +176,11 @@ // somewhere. void AddColumnResult(scoped_refptr<const NGLayoutResult>, const NGBlockBreakToken* incoming_break_token) const; + // Add a column layout result to this node. + void AddColumnResult(scoped_refptr<const NGLayoutResult>) const; + // Replace an existing column layout result with a new one. + void ReplaceColumnResult(scoped_refptr<const NGLayoutResult>, + const NGPhysicalBoxFragment& old_fragment) const; static bool CanUseNewLayout(const LayoutBox&); bool CanUseNewLayout() const;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc index c084d4e..8c03202 100644 --- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc +++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -1225,11 +1225,14 @@ offset = fragmentainer.offset; offset.inline_offset += column_inline_progression_; } - container_builder_->AddChild(algorithm.Layout()->PhysicalFragment(), - offset); + scoped_refptr<const NGLayoutResult> new_result = algorithm.Layout(); + node.AddColumnResult(new_result); + container_builder_->AddChild(new_result->PhysicalFragment(), offset); } else { - container_builder_->ReplaceChild( - index, algorithm.Layout()->PhysicalFragment(), fragmentainer.offset); + scoped_refptr<const NGLayoutResult> new_result = algorithm.Layout(); + node.ReplaceColumnResult(new_result, fragment); + container_builder_->ReplaceChild(index, new_result->PhysicalFragment(), + fragmentainer.offset); } }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc index 1503e220..4b9eb71 100644 --- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc +++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
@@ -375,6 +375,22 @@ return builder.ToBoxFragment(); } +const LayoutBox* NGPhysicalBoxFragment::OwnerLayoutBox() const { + const LayoutBox* owner_box = + DynamicTo<LayoutBox>(GetSelfOrContainerLayoutObject()); + DCHECK(owner_box); + if (UNLIKELY(IsColumnBox())) { + owner_box = To<LayoutBox>(owner_box->SlowFirstChild()); + DCHECK(owner_box && owner_box->IsLayoutFlowThread()); + } + DCHECK(owner_box->PhysicalFragments().Contains(*this)); + return owner_box; +} + +LayoutBox* NGPhysicalBoxFragment::MutableOwnerLayoutBox() const { + return const_cast<LayoutBox*>(OwnerLayoutBox()); +} + const NGPhysicalBoxFragment* NGPhysicalBoxFragment::PostLayout() const { const auto* layout_object = GetSelfOrContainerLayoutObject(); if (UNLIKELY(!layout_object)) {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h index b0356143..0d3f5b7 100644 --- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h +++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
@@ -206,6 +206,11 @@ return is_inline_formatting_context_; } + // The |LayoutBox| whose |PhysicalFragments()| contains |this|. This is + // different from |GetLayoutObject()| if |this.IsColumnBox()|. + const LayoutBox* OwnerLayoutBox() const; + LayoutBox* MutableOwnerLayoutBox() const; + PhysicalRect ScrollableOverflow(TextHeightType height_type) const; PhysicalRect ScrollableOverflowFromChildren(TextHeightType height_type) const;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc index 036a8ce..7fcf63c0 100644 --- a/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc +++ b/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc
@@ -156,7 +156,11 @@ } else { LayoutUnit old_block_size = NGFragment(writing_direction_, physical_fragment).BlockSize(); - DCHECK_EQ(old_block_size, new_block_size); +#if DCHECK_IS_ON() + // Tables don't respect the typical block-sizing rules. + if (!physical_fragment.IsTableNG()) + DCHECK_EQ(old_block_size, new_block_size); +#endif container_builder_.SetFragmentBlockSize(old_block_size); }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc index aa04c58..4c0508e 100644 --- a/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc +++ b/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc
@@ -30,9 +30,12 @@ if (is_new_fragment) { children_ = {}; iterator_ = children_.end(); + container_builder_.SetIsFirstForNode(false); return; } + container_builder_.SetIsFirstForNode(fragment.IsFirstForNode()); + // We need the previous physical container size to calculate the position of // any child fragments. previous_physical_container_size_ = fragment.Size();
diff --git a/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h b/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h index dd5e111..adb661f 100644 --- a/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h +++ b/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h
@@ -13,6 +13,7 @@ class Document; class Element; class HTMLFrameOwnerElement; +class Node; // Manages the root scroller associated with a given document. The root // scroller causes browser controls movement, overscroll effects and prevents
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc index 529c167..44c8f9c 100644 --- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc +++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -737,6 +737,13 @@ containing_block_context = &context.current; containing_block_context->paint_offset += offset; } + + // Check |box_fragment| and |LayoutBox| who produced it are in sync. + DCHECK(box_fragment->OwnerLayoutBox()); + DCHECK_EQ(box_fragment->IsFirstForNode(), + box_fragment == + box_fragment->OwnerLayoutBox()->GetPhysicalFragment(0)); + WalkChildren(/* parent */ nullptr, iterator); if (containing_block_context) containing_block_context->paint_offset -= offset;
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h index b570332..15516a5 100644 --- a/third_party/blink/renderer/core/style/computed_style.h +++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -2264,8 +2264,10 @@ // Table layout utility functions. bool IsFixedTableLayout() const { + // https://www.w3.org/TR/css-tables-3/#table-layout-property return TableLayout() == ETableLayout::kFixed && - !LogicalWidth().IsAutoOrContentOrIntrinsic(); + (LogicalWidth().IsSpecified() || LogicalWidth().IsMinContent() || + LogicalWidth().IsFitContent()); } LogicalSize TableBorderSpacing() const {
diff --git a/third_party/blink/renderer/core/style/list_style_type_data.cc b/third_party/blink/renderer/core/style/list_style_type_data.cc index 92f22d2..16c299e 100644 --- a/third_party/blink/renderer/core/style/list_style_type_data.cc +++ b/third_party/blink/renderer/core/style/list_style_type_data.cc
@@ -4,7 +4,10 @@ #include "third_party/blink/renderer/core/style/list_style_type_data.h" +#include "third_party/blink/renderer/core/css/counter_style.h" #include "third_party/blink/renderer/core/css/css_value_id_mappings.h" +#include "third_party/blink/renderer/core/css/style_engine.h" +#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/tree_scope.h" #include "third_party/blink/renderer/core/style/content_data.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" @@ -44,6 +47,7 @@ void ListStyleTypeData::Trace(Visitor* visitor) const { visitor->Trace(tree_scope_); + visitor->Trace(counter_style_); } // static @@ -73,4 +77,31 @@ return CounterStyleNameToDeprecatedEnum(ListStyle()); } +bool ListStyleTypeData::IsCounterStyleReferenceValid(Document& document) const { + if (!IsCounterStyle()) { + DCHECK(!counter_style_); + return true; + } + + if (!counter_style_ || counter_style_->IsDirty()) + return false; + + // Even if the referenced counter style is clean, it may still be stale if new + // counter styles have been inserted, in which case the same (scope, name) now + // refers to a different counter style. So we make an extra lookup to verify. + return counter_style_ == + &document.GetStyleEngine().FindCounterStyleAcrossScopes( + GetCounterStyleName(), GetTreeScope()); +} + +const CounterStyle& ListStyleTypeData::GetCounterStyle( + Document& document) const { + DCHECK(IsCounterStyle()); + if (!IsCounterStyleReferenceValid(document)) { + counter_style_ = document.GetStyleEngine().FindCounterStyleAcrossScopes( + GetCounterStyleName(), GetTreeScope()); + } + return *counter_style_; +} + } // namespace blink
diff --git a/third_party/blink/renderer/core/style/list_style_type_data.h b/third_party/blink/renderer/core/style/list_style_type_data.h index 3922b29..118f7985 100644 --- a/third_party/blink/renderer/core/style/list_style_type_data.h +++ b/third_party/blink/renderer/core/style/list_style_type_data.h
@@ -11,6 +11,8 @@ namespace blink { +class CounterStyle; +class Document; class TreeScope; class ListStyleTypeData final : public GarbageCollected<ListStyleTypeData> { @@ -55,6 +57,10 @@ const TreeScope* GetTreeScope() const { return tree_scope_; } + // TODO(crbug.com/687225): Try not to pass a Document, which is cumbersome. + bool IsCounterStyleReferenceValid(Document&) const; + const CounterStyle& GetCounterStyle(Document&) const; + EListStyleType ToDeprecatedListStyleTypeEnum() const; private: @@ -63,6 +69,10 @@ // The tree scope for looking up the custom counter style name Member<const TreeScope> tree_scope_; + + // The CounterStyle that we are using. The reference is updated on demand. + // Note: this is NOT part of the computed value of 'list-style-type'. + mutable Member<const CounterStyle> counter_style_; }; } // namespace blink
diff --git a/third_party/blink/renderer/core/timing/performance_long_task_timing.h b/third_party/blink/renderer/core/timing/performance_long_task_timing.h index 2132ed1..fb00065 100644 --- a/third_party/blink/renderer/core/timing/performance_long_task_timing.h +++ b/third_party/blink/renderer/core/timing/performance_long_task_timing.h
@@ -30,6 +30,7 @@ const AtomicString& culprit_src, const AtomicString& culprit_id, const AtomicString& culprit_name); + ~PerformanceLongTaskTiming() override; AtomicString entryType() const override; PerformanceEntryType EntryTypeEnum() const override; @@ -39,8 +40,6 @@ void Trace(Visitor*) const override; private: - ~PerformanceLongTaskTiming() override; - void BuildJSONValue(V8ObjectBuilder&) const override; TaskAttributionVector attribution_;
diff --git a/third_party/blink/renderer/core/timing/performance_navigation_timing.h b/third_party/blink/renderer/core/timing/performance_navigation_timing.h index c566ccb..22eb990 100644 --- a/third_party/blink/renderer/core/timing/performance_navigation_timing.h +++ b/third_party/blink/renderer/core/timing/performance_navigation_timing.h
@@ -35,6 +35,7 @@ ResourceTimingInfo*, base::TimeTicks time_origin, HeapVector<Member<PerformanceServerTiming>>); + ~PerformanceNavigationTiming() override; // Attributes inherited from PerformanceEntry. DOMHighResTimeStamp duration() const override; @@ -67,8 +68,6 @@ void BuildJSONValue(V8ObjectBuilder&) const override; private: - ~PerformanceNavigationTiming() override; - static AtomicString GetNavigationType(WebNavigationType, const Document*); const DocumentTiming* GetDocumentTiming() const;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_media_element.cc b/third_party/blink/renderer/modules/accessibility/ax_media_element.cc index 8abff1fd..e12b63f 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_media_element.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_media_element.cc
@@ -15,6 +15,7 @@ LayoutObject* layout_object, AXObjectCacheImpl& ax_object_cache) { DCHECK(layout_object->GetNode()); + DCHECK(IsA<HTMLMediaElement>(layout_object->GetNode())); return MakeGarbageCollected<AccessibilityMediaElement>(layout_object, ax_object_cache); } @@ -60,6 +61,11 @@ bool AccessibilityMediaElement::HasControls() const { if (IsDetached()) return false; + if (!IsA<HTMLMediaElement>(GetNode()) || !GetNode()->isConnected()) { + NOTREACHED() << "Accessible media element not ready: " << GetNode() + << " isConnected? " << GetNode()->isConnected(); + return false; + } return To<HTMLMediaElement>(GetNode())->ShouldShowControls(); }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc index 755821d..3a7f1bb4 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -3410,8 +3410,7 @@ int new_index = index; for (wtf_size_t i = 0; i < length; ++i) { // If the child was owned, it will be added elsewhere as a direct - // child of the object owning it, and not as an indirect child under - // an object not included in the tree. + // child of the object owning it. if (!AXObjectCache().IsAriaOwned(children[i])) { DCHECK(!children[i]->IsDetached()) << "Cannot add a detached child: " << children[i]->ToString(true, true);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc index a9eec38..b54520f 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_object.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -2474,10 +2474,6 @@ return parent->AncestorExposesActiveDescendant(); } -bool AXObject::HasIndirectChildren() const { - return RoleValue() == ax::mojom::blink::Role::kTableHeaderContainer; -} - bool AXObject::CanSetSelectedAttribute() const { // Sub-widget elements can be selected if not disabled (native or ARIA) return IsSubWidget() && Restriction() != kRestrictionDisabled; @@ -3309,16 +3305,12 @@ } int AXObject::ChildCountIncludingIgnored() const { - return HasIndirectChildren() ? 0 : int{ChildrenIncludingIgnored().size()}; + return int{ChildrenIncludingIgnored().size()}; } AXObject* AXObject::ChildAtIncludingIgnored(int index) const { - // We need to use "ChildCountIncludingIgnored()" and - // "ChildrenIncludingIgnored()" instead of using the "children_" member - // directly, because we might need to update children and check for the - // presence of indirect children. - if (index < 0 || index >= ChildCountIncludingIgnored()) - return nullptr; + DCHECK_GE(index, 0); + DCHECK_LT(index, ChildCountIncludingIgnored()); return ChildrenIncludingIgnored()[index]; } @@ -4701,7 +4693,6 @@ case ax::mojom::blink::Role::kCaret: case ax::mojom::blink::Role::kClient: case ax::mojom::blink::Role::kColorWell: - case ax::mojom::blink::Role::kColumn: case ax::mojom::blink::Role::kComboBoxMenuButton: // Only value from // content. case ax::mojom::blink::Role::kComboBoxGrouping: @@ -4796,7 +4787,6 @@ case ax::mojom::blink::Role::kSuggestion: case ax::mojom::blink::Role::kSvgRoot: case ax::mojom::blink::Role::kTable: - case ax::mojom::blink::Role::kTableHeaderContainer: case ax::mojom::blink::Role::kTabList: case ax::mojom::blink::Role::kTabPanel: case ax::mojom::blink::Role::kTerm: @@ -4920,8 +4910,10 @@ return is_inside_portal && is_main_frame; } + case ax::mojom::blink::Role::kColumn: + case ax::mojom::blink::Role::kTableHeaderContainer: case ax::mojom::blink::Role::kUnknown: - NOTREACHED() << "Illegal Role::kUnknown for " << ToString(true, true); + NOTREACHED() << "Role shouldn't occur in Blink: " << ToString(true, true); break; }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_position.cc b/third_party/blink/renderer/modules/accessibility/ax_position.cc index 17268a2b..783a72e 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_position.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_position.cc
@@ -392,6 +392,9 @@ const AXObject* AXPosition::ChildAfterTreePosition() const { if (!IsValid() || IsTextPosition()) return nullptr; + if (ChildIndex() == container_object_->ChildCountIncludingIgnored()) + return nullptr; + DCHECK_LT(ChildIndex(), container_object_->ChildCountIncludingIgnored()); return container_object_->ChildAtIncludingIgnored(ChildIndex()); }
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 e8b9146..fa49edc 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
@@ -145,11 +145,11 @@ DCHECK(state_stack_.begin() < state_stack_.end()); for (curr_state = state_stack_.begin(); curr_state < state_stack_.end(); curr_state++) { - c->setMatrix(SkMatrix::I()); + c->setMatrix(SkM44()); if (curr_state->Get()) { curr_state->Get()->PlaybackClips(c); c->setMatrix( - TransformationMatrixToSkMatrix(curr_state->Get()->GetTransform())); + TransformationMatrix::ToSkM44(curr_state->Get()->GetTransform())); } c->save(); } @@ -593,7 +593,7 @@ if (!GetState().IsTransformInvertible()) return; - c->concat(TransformationMatrixToSkMatrix(transform)); + c->concat(TransformationMatrix::ToSkM44(transform)); path_.Transform(transform.Inverse()); }
diff --git a/third_party/blink/renderer/modules/webaudio/oscillator_options.idl b/third_party/blink/renderer/modules/webaudio/oscillator_options.idl index d9e7ef88b..caa24013 100644 --- a/third_party/blink/renderer/modules/webaudio/oscillator_options.idl +++ b/third_party/blink/renderer/modules/webaudio/oscillator_options.idl
@@ -7,5 +7,5 @@ OscillatorType type = "sine"; float detune = 0; float frequency = 440; - PeriodicWave? periodicWave; -}; \ No newline at end of file + PeriodicWave periodicWave; +};
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker_test.cc b/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker_test.cc index a10395a9..3a7869c 100644 --- a/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker_test.cc +++ b/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker_test.cc
@@ -143,6 +143,12 @@ std::move(callback).Run(mojo::NullRemote(), base::nullopt, mojo::NullRemote(), "CDM creation not supported"); } +#if defined(OS_WIN) + void CreateMediaFoundationRenderer( + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) override {} +#endif // defined(OS_WIN) private: media::MojoCdmServiceContext cdm_service_context_;
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc index ea0d524ed..60ea9d8 100644 --- a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc +++ b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc
@@ -149,6 +149,13 @@ mojo::NullRemote(), "CDM creation not supported"); } +#if defined(OS_WIN) + void CreateMediaFoundationRenderer( + mojo::PendingReceiver<media::mojom::Renderer> receiver, + mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> + renderer_extension_receiver) override {} +#endif // defined(OS_WIN) + private: media::MojoCdmServiceContext cdm_service_context_; FakeMojoMediaClient mojo_media_client_;
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.cc b/third_party/blink/renderer/modules/webcodecs/video_frame.cc index e872d63..03378c0 100644 --- a/third_party/blink/renderer/modules/webcodecs/video_frame.cc +++ b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
@@ -14,16 +14,19 @@ #include "media/base/timestamp_constants.h" #include "media/base/video_frame.h" #include "media/base/video_frame_metadata.h" +#include "media/base/video_frame_pool.h" #include "media/base/wait_and_replace_sync_token_client.h" #include "media/renderers/paint_canvas_video_renderer.h" #include "media/renderers/video_frame_yuv_converter.h" #include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/bindings/modules/v8/v8_plane_init.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame_init.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_video_pixel_format.h" #include "third_party/blink/renderer/core/html/canvas/image_data.h" #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h" #include "third_party/blink/renderer/core/imagebitmap/image_bitmap_factories.h" #include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/typed_arrays/dom_array_piece.h" #include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h" #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" #include "third_party/blink/renderer/platform/graphics/image.h" @@ -102,6 +105,103 @@ std::move(async_result)))); } +media::VideoPixelFormat ToMediaPixelFormat(V8VideoPixelFormat::Enum fmt) { + switch (fmt) { + case V8VideoPixelFormat::Enum::kI420: + return media::PIXEL_FORMAT_I420; + case V8VideoPixelFormat::Enum::kNV12: + return media::PIXEL_FORMAT_NV12; + case V8VideoPixelFormat::Enum::kABGR: + return media::PIXEL_FORMAT_ABGR; + case V8VideoPixelFormat::Enum::kXBGR: + return media::PIXEL_FORMAT_XBGR; + case V8VideoPixelFormat::Enum::kARGB: + return media::PIXEL_FORMAT_ARGB; + case V8VideoPixelFormat::Enum::kXRGB: + return media::PIXEL_FORMAT_XRGB; + } +} + +class CachedVideoFramePool : public GarbageCollected<CachedVideoFramePool>, + public Supplement<ExecutionContext> { + public: + static const char kSupplementName[]; + + static CachedVideoFramePool& From(ExecutionContext& context) { + CachedVideoFramePool* supplement = + Supplement<ExecutionContext>::From<CachedVideoFramePool>(context); + if (!supplement) { + supplement = MakeGarbageCollected<CachedVideoFramePool>(context); + Supplement<ExecutionContext>::ProvideTo(context, supplement); + } + return *supplement; + } + + explicit CachedVideoFramePool(ExecutionContext& context) + : Supplement<ExecutionContext>(context), + task_runner_(Thread::Current()->GetTaskRunner()) {} + virtual ~CachedVideoFramePool() = default; + + // Disallow copy and assign. + CachedVideoFramePool& operator=(const CachedVideoFramePool&) = delete; + CachedVideoFramePool(const CachedVideoFramePool&) = delete; + + scoped_refptr<media::VideoFrame> CreateFrame(media::VideoPixelFormat format, + const gfx::Size& coded_size, + const gfx::Rect& visible_rect, + const gfx::Size& natural_size, + base::TimeDelta timestamp) { + if (!frame_pool_) + CreatePoolAndStartIdleObsever(); + + last_frame_creation_ = base::TimeTicks::Now(); + return frame_pool_->CreateFrame(format, coded_size, visible_rect, + natural_size, timestamp); + } + + void Trace(Visitor* visitor) const override { + Supplement<ExecutionContext>::Trace(visitor); + } + + private: + static const base::TimeDelta kIdleTimeout; + + void PostMonitoringTask() { + DCHECK(!task_handle_.IsActive()); + task_handle_ = PostDelayedCancellableTask( + *task_runner_, FROM_HERE, + WTF::Bind(&CachedVideoFramePool::PurgeIdleFramePool, + WrapWeakPersistent(this)), + kIdleTimeout); + } + + void CreatePoolAndStartIdleObsever() { + DCHECK(!frame_pool_); + frame_pool_ = std::make_unique<media::VideoFramePool>(); + PostMonitoringTask(); + } + + // We don't want a VideoFramePool to stick around forever wasting memory, so + // once we haven't issued any VideoFrames for a while, turn down the pool. + void PurgeIdleFramePool() { + if (base::TimeTicks::Now() - last_frame_creation_ > kIdleTimeout) { + frame_pool_.reset(); + return; + } + PostMonitoringTask(); + } + + scoped_refptr<base::SequencedTaskRunner> task_runner_; + std::unique_ptr<media::VideoFramePool> frame_pool_; + base::TimeTicks last_frame_creation_; + TaskHandle task_handle_; +}; + +// static -- defined out of line to satisfy link time requirements. +const char CachedVideoFramePool::kSupplementName[] = "CachedVideoFramePool"; +const base::TimeDelta CachedVideoFramePool::kIdleTimeout = + base::TimeDelta::FromSeconds(10); + } // namespace VideoFrame::VideoFrame(scoped_refptr<media::VideoFrame> frame, @@ -229,6 +329,177 @@ } // static +VideoFrame* VideoFrame::Create(ScriptState* script_state, + const String& format, + const HeapVector<Member<PlaneInit>>& planes, + VideoFrameInit* init, + ExceptionState& exception_state) { + if (!init->hasCodedWidth() || !init->hasCodedHeight()) { + exception_state.ThrowDOMException( + DOMExceptionCode::kConstraintError, + "Coded size is required for planar construction"); + return nullptr; + } + + // Type formats are enforced by V8. + auto typed_fmt = V8VideoPixelFormat::Create(format); + DCHECK(typed_fmt); + + auto media_fmt = ToMediaPixelFormat(typed_fmt->AsEnum()); + + // There's no I420A pixel format, so treat I420 + 4 planes as I420A. + if (media_fmt == media::PIXEL_FORMAT_I420 && planes.size() == 4u) + media_fmt = media::PIXEL_FORMAT_I420A; + + if (media::VideoFrame::NumPlanes(media_fmt) != planes.size()) { + exception_state.ThrowDOMException( + DOMExceptionCode::kConstraintError, + String::Format("Invalid number of planes for format %s; expected %zu, " + "received %u", + format.Ascii().c_str(), + media::VideoFrame::NumPlanes(media_fmt), planes.size())); + return nullptr; + } + + const gfx::Size coded_size(init->codedWidth(), init->codedHeight()); + if (coded_size.IsEmpty()) { + exception_state.ThrowDOMException( + DOMExceptionCode::kConstraintError, + String::Format("Invalid coded size (%d, %d) provided", + init->codedWidth(), init->codedHeight())); + return nullptr; + } + + for (size_t i = 0; i < planes.size(); ++i) { + const auto minimum_size = + media::VideoFrame::PlaneSize(media_fmt, i, coded_size); + if (planes[i]->stride() < uint32_t{minimum_size.width()}) { + exception_state.ThrowDOMException( + DOMExceptionCode::kConstraintError, + String::Format( + "The stride of plane %zu is too small for the given coded size " + "(%s); expected at least %d, received %u", + i, coded_size.ToString().c_str(), minimum_size.width(), + planes[i]->stride())); + return nullptr; + } + if (planes[i]->rows() != uint32_t{minimum_size.height()}) { + exception_state.ThrowDOMException( + DOMExceptionCode::kConstraintError, + String::Format( + "The row count for plane %zu is incorrect for the given coded " + "size (%s); expected %d, received %u", + i, coded_size.ToString().c_str(), minimum_size.height(), + planes[i]->rows())); + return nullptr; + } + + // This requires the full stride to be provided for every row. + gfx::Size provided_size(planes[i]->stride(), planes[i]->rows()); + const auto required_byte_size = provided_size.GetCheckedArea(); + if (!required_byte_size.IsValid()) { + exception_state.ThrowDOMException( + DOMExceptionCode::kConstraintError, + String::Format("The size of plane %zu is too large", i)); + return nullptr; + } + + DOMArrayPiece buffer(planes[i]->src()); + if (buffer.ByteLength() < required_byte_size.ValueOrDie()) { + // Note: We use GetArea() below instead of area.ValueOrDie() since the + // base::StrictNumeric seems to confuse the printf() format checks. + exception_state.ThrowDOMException( + DOMExceptionCode::kConstraintError, + String::Format( + "The size of plane %zu is too small for the given coded " + "size (%s); expected at least %d, received %zu", + i, coded_size.ToString().c_str(), provided_size.GetArea(), + buffer.ByteLength())); + return nullptr; + } + } + + auto visible_rect = gfx::Rect(coded_size); + if (init->hasCropLeft() || init->hasCropTop() || init->hasCropWidth() || + init->hasCropHeight()) { + const auto crop_left = init->hasCropLeft() ? init->cropLeft() : 0; + const auto crop_top = init->hasCropTop() ? init->cropTop() : 0; + const auto crop_w = + init->hasCropWidth() ? visible_rect.width() - init->cropWidth() : 0; + const auto crop_h = + init->hasCropHeight() ? visible_rect.height() - init->cropHeight() : 0; + if (crop_w < 0 || crop_h < 0 || crop_w > unsigned{visible_rect.width()} || + crop_h > unsigned{visible_rect.height()}) { + visible_rect = gfx::Rect(); + } else { + visible_rect.Inset(crop_left, crop_top, crop_w, crop_h); + } + + if (visible_rect.IsEmpty()) { + exception_state.ThrowDOMException( + DOMExceptionCode::kConstraintError, + String::Format( + "Invalid visble rect (%s) after crop (%d, %d, %d, %d) applied", + visible_rect.ToString().c_str(), crop_left, crop_top, crop_w, + crop_h)); + return nullptr; + } + } + + auto natural_size = visible_rect.size(); + if (init->hasDisplayWidth()) + natural_size.set_width(init->displayWidth()); + if (init->hasDisplayHeight()) + natural_size.set_height(init->displayHeight()); + if (coded_size.IsEmpty()) { + exception_state.ThrowDOMException( + DOMExceptionCode::kConstraintError, + String::Format("Invalid display size (%s) provided", + natural_size.ToString().c_str())); + return nullptr; + } + + const auto timestamp = base::TimeDelta::FromMicroseconds(init->timestamp()); + auto& frame_pool = + CachedVideoFramePool::From(*ExecutionContext::From(script_state)); + auto frame = frame_pool.CreateFrame(media_fmt, coded_size, visible_rect, + natural_size, timestamp); + if (!frame) { + exception_state.ThrowDOMException( + DOMExceptionCode::kConstraintError, + String::Format( + "Failed to create a video frame with configuration {format:%s, " + "coded_size:%s, visible_rect:%s, display_size:%s}", + VideoPixelFormatToString(media_fmt).c_str(), + coded_size.ToString().c_str(), visible_rect.ToString().c_str(), + natural_size.ToString().c_str())); + return nullptr; + } + + for (size_t i = 0; i < planes.size(); ++i) { + const auto minimum_size = + media::VideoFrame::PlaneSize(media_fmt, i, coded_size); + + DOMArrayPiece buffer(planes[i]->src()); + + uint8_t* dest_ptr = frame->visible_data(i); + const uint8_t* src_ptr = reinterpret_cast<uint8_t*>(buffer.Data()); + for (size_t r = 0; r < planes[i]->rows(); ++r) { + DCHECK_LE( + src_ptr + planes[i]->stride(), + reinterpret_cast<uint8_t*>(buffer.Data()) + buffer.ByteLength()); + + memcpy(dest_ptr, src_ptr, minimum_size.width()); + src_ptr += planes[i]->stride(); + dest_ptr += frame->stride(i); + } + } + + return MakeGarbageCollected<VideoFrame>(std::move(frame), + ExecutionContext::From(script_state)); +} + +// static bool VideoFrame::IsSupportedPlanarFormat(media::VideoFrame* frame) { if (!frame) return false;
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.h b/third_party/blink/renderer/modules/webcodecs/video_frame.h index 94d70fe..b8421b9 100644 --- a/third_party/blink/renderer/modules/webcodecs/video_frame.h +++ b/third_party/blink/renderer/modules/webcodecs/video_frame.h
@@ -23,6 +23,7 @@ class ImageBitmap; class ExceptionState; class ExecutionContext; +class PlaneInit; class ScriptPromise; class ScriptState; class VideoFrameInit; @@ -45,6 +46,11 @@ ImageBitmap*, VideoFrameInit*, ExceptionState&); + static VideoFrame* Create(ScriptState*, + const String& format, + const HeapVector<Member<PlaneInit>>& planes, + VideoFrameInit* init, + ExceptionState&); String format() const; base::Optional<HeapVector<Member<Plane>>> planes();
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.idl b/third_party/blink/renderer/modules/webcodecs/video_frame.idl index ed293de..fa99541 100644 --- a/third_party/blink/renderer/modules/webcodecs/video_frame.idl +++ b/third_party/blink/renderer/modules/webcodecs/video_frame.idl
@@ -9,7 +9,12 @@ Serializable, RuntimeEnabled=WebCodecs ] interface VideoFrame { - [CallWith=ScriptState, RaisesException] constructor(ImageBitmap source, VideoFrameInit init); + [CallWith=ScriptState, RaisesException] + constructor(ImageBitmap source, VideoFrameInit init); + [CallWith=ScriptState, RaisesException] + constructor(VideoPixelFormat format, + sequence<PlaneInit> planes, + VideoFrameInit init); // TODO(sandersd): Provide a way to find out what pixel formats are supported. // TODO(sandersd): Provide a way to convert to a specific pixel format, and to @@ -59,6 +64,6 @@ // Create an ImageBitmap from the crop region, scaled to the display size. // TODO(sandersd): Should use the global createImageBitmap() instead. - [CallWith=ScriptState, RaisesException] Promise<ImageBitmap> createImageBitmap( - optional ImageBitmapOptions options = {}); + [CallWith=ScriptState, RaisesException] Promise<ImageBitmap> + createImageBitmap(optional ImageBitmapOptions options = {}); };
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame_init.idl b/third_party/blink/renderer/modules/webcodecs/video_frame_init.idl index b1c7c21a..17dee99 100644 --- a/third_party/blink/renderer/modules/webcodecs/video_frame_init.idl +++ b/third_party/blink/renderer/modules/webcodecs/video_frame_init.idl
@@ -7,4 +7,13 @@ dictionary VideoFrameInit { required unsigned long long timestamp; // microseconds unsigned long long duration; // microseconds -}; \ No newline at end of file + + unsigned long codedWidth; + unsigned long codedHeight; + unsigned long cropLeft; + unsigned long cropTop; + unsigned long cropWidth; + unsigned long cropHeight; + unsigned long displayWidth; + unsigned long displayHeight; +};
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.cc b/third_party/blink/renderer/modules/webgpu/gpu_device.cc index 5642879..dc35b63 100644 --- a/third_party/blink/renderer/modules/webgpu/gpu_device.cc +++ b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
@@ -33,6 +33,7 @@ #include "third_party/blink/renderer/modules/webgpu/gpu_texture.h" #include "third_party/blink/renderer/modules/webgpu/gpu_uncaptured_error_event.h" #include "third_party/blink/renderer/modules/webgpu/gpu_validation_error.h" +#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler.h" #include "third_party/blink/renderer/platform/heap/heap.h" namespace blink {
diff --git a/third_party/blink/renderer/modules/xr/BUILD.gn b/third_party/blink/renderer/modules/xr/BUILD.gn index bcf82f00..7f0c302 100644 --- a/third_party/blink/renderer/modules/xr/BUILD.gn +++ b/third_party/blink/renderer/modules/xr/BUILD.gn
@@ -17,6 +17,8 @@ "xr_bounded_reference_space.h", "xr_canvas_input_provider.cc", "xr_canvas_input_provider.h", + "xr_cpu_depth_information.cc", + "xr_cpu_depth_information.h", "xr_cube_map.cc", "xr_cube_map.h", "xr_depth_information.cc", @@ -106,6 +108,8 @@ "xr_viewport.h", "xr_webgl_binding.cc", "xr_webgl_binding.h", + "xr_webgl_depth_information.cc", + "xr_webgl_depth_information.h", "xr_webgl_layer.cc", "xr_webgl_layer.h", "xr_webgl_rendering_context.h",
diff --git a/third_party/blink/renderer/modules/xr/idls.gni b/third_party/blink/renderer/modules/xr/idls.gni index 97a038bc..e542fe2 100644 --- a/third_party/blink/renderer/modules/xr/idls.gni +++ b/third_party/blink/renderer/modules/xr/idls.gni
@@ -8,6 +8,7 @@ "xr_anchor.idl", "xr_anchor_set.idl", "xr_bounded_reference_space.idl", + "xr_cpu_depth_information.idl", "xr_depth_information.idl", "xr_dom_overlay_state.idl", "xr_frame.idl", @@ -42,12 +43,14 @@ "xr_viewer_pose.idl", "xr_viewport.idl", "xr_webgl_binding.idl", + "xr_webgl_depth_information.idl", "xr_webgl_layer.idl", ] modules_callback_function_idl_files = [ "xr_frame_request_callback.idl" ] modules_dictionary_idl_files = [ + "xr_depth_state_init.idl", "xr_dom_overlay_init.idl", "xr_hit_test_options_init.idl", "xr_input_source_event_init.idl",
diff --git a/third_party/blink/renderer/modules/xr/xr_cpu_depth_information.cc b/third_party/blink/renderer/modules/xr/xr_cpu_depth_information.cc new file mode 100644 index 0000000..c02306c --- /dev/null +++ b/third_party/blink/renderer/modules/xr/xr_cpu_depth_information.cc
@@ -0,0 +1,85 @@ +// 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 "third_party/blink/renderer/modules/xr/xr_cpu_depth_information.h" + +#include <cstdlib> + +#include "base/numerics/checked_math.h" +#include "third_party/blink/renderer/core/dom/dom_exception.h" +#include "third_party/blink/renderer/platform/bindings/exception_state.h" + +namespace { +constexpr char kOutOfBoundsAccess[] = + "Attempted to access data that is out-of-bounds."; +} + +namespace blink { + +XRCPUDepthInformation::XRCPUDepthInformation( + const XRFrame* xr_frame, + const gfx::Size& size, + const gfx::Transform& norm_texture_from_norm_view, + float raw_value_to_meters, + DOMUint16Array* data) + : XRDepthInformation(xr_frame, + size, + norm_texture_from_norm_view, + raw_value_to_meters), + data_(data) { + DVLOG(3) << __func__; + + CHECK_EQ(base::CheckMul(2, size_.width(), size_.height()).ValueOrDie(), + data_->byteLength()); +} + +DOMUint16Array* XRCPUDepthInformation::data( + ExceptionState& exception_state) const { + if (!ValidateFrame(exception_state)) { + return nullptr; + } + + return data_; +} + +float XRCPUDepthInformation::getDepthInMeters( + uint32_t column, + uint32_t row, + ExceptionState& exception_state) const { + DVLOG(3) << __func__ << ": column=" << column << ", row=" << row; + + if (!ValidateFrame(exception_state)) { + return 0.0; + } + + if (column >= static_cast<size_t>(size_.width())) { + exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError, + kOutOfBoundsAccess); + return 0.0; + } + + if (row >= static_cast<size_t>(size_.height())) { + exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError, + kOutOfBoundsAccess); + return 0.0; + } + + auto checked_index = + base::CheckAdd(column, base::CheckMul(row, size_.width())); + size_t index = checked_index.ValueOrDie(); + + // Convert from data's native units to meters when accessing: + float result = data_->Item(index) * rawValueToMeters_; + + DVLOG(3) << __func__ << ": index=" << index << ", result=" << result; + + return result; +} + +void XRCPUDepthInformation::Trace(Visitor* visitor) const { + visitor->Trace(data_); + XRDepthInformation::Trace(visitor); +} + +} // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_cpu_depth_information.h b/third_party/blink/renderer/modules/xr/xr_cpu_depth_information.h new file mode 100644 index 0000000..790918c --- /dev/null +++ b/third_party/blink/renderer/modules/xr/xr_cpu_depth_information.h
@@ -0,0 +1,46 @@ +// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_CPU_DEPTH_INFORMATION_H_ +#define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_CPU_DEPTH_INFORMATION_H_ + +#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h" +#include "third_party/blink/renderer/modules/xr/xr_depth_information.h" + +namespace gfx { +class Size; +class Transform; +} // namespace gfx + +namespace blink { + +class ExceptionState; +class XRFrame; + +class XRCPUDepthInformation final : public XRDepthInformation { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit XRCPUDepthInformation( + const XRFrame* xr_frame, + const gfx::Size& size, + const gfx::Transform& norm_texture_from_norm_view, + float raw_value_to_meters, + DOMUint16Array* data); + + DOMUint16Array* data(ExceptionState& exception_state) const; + + float getDepthInMeters(uint32_t column, + uint32_t row, + ExceptionState& exception_state) const; + + void Trace(Visitor* visitor) const override; + + private: + const Member<DOMUint16Array> data_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_CPU_DEPTH_INFORMATION_H_
diff --git a/third_party/blink/renderer/modules/xr/xr_cpu_depth_information.idl b/third_party/blink/renderer/modules/xr/xr_cpu_depth_information.idl new file mode 100644 index 0000000..b2a98d2 --- /dev/null +++ b/third_party/blink/renderer/modules/xr/xr_cpu_depth_information.idl
@@ -0,0 +1,16 @@ +// 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. + +[ + SecureContext, + Exposed=Window, + RuntimeEnabled=WebXRDepth +] interface XRCPUDepthInformation : XRDepthInformation { + [RaisesException, SameObject, MeasureAs=XRCPUDepthInformationDataAttribute] + readonly attribute DataView data; + + [RaisesException, MeasureAs=XRCPUDepthInformationGetDepth] + float getDepthInMeters(unsigned long column, unsigned long row); +}; +
diff --git a/third_party/blink/renderer/modules/xr/xr_depth_information.cc b/third_party/blink/renderer/modules/xr/xr_depth_information.cc index 77bae6c..e55237c 100644 --- a/third_party/blink/renderer/modules/xr/xr_depth_information.cc +++ b/third_party/blink/renderer/modules/xr/xr_depth_information.cc
@@ -6,8 +6,6 @@ #include <cstdlib> -#include "base/numerics/checked_math.h" -#include "device/vr/public/mojom/vr_service.mojom-blink.h" #include "third_party/blink/renderer/core/dom/dom_exception.h" #include "third_party/blink/renderer/modules/xr/xr_frame.h" #include "third_party/blink/renderer/modules/xr/xr_rigid_transform.h" @@ -15,8 +13,7 @@ #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h" namespace { -constexpr char kOutOfBoundsAccess[] = - "Attempted to access data that is out-of-bounds."; + constexpr char kFrameInactive[] = "XRDepthInformation members are only accessible when their XRFrame's " "`active` boolean is `true`."; @@ -31,26 +28,15 @@ const XRFrame* xr_frame, const gfx::Size& size, const gfx::Transform& norm_texture_from_norm_view, - DOMUint16Array* data) + float raw_value_to_meters) : xr_frame_(xr_frame), size_(size), - data_(data), - norm_texture_from_norm_view_(norm_texture_from_norm_view) { + norm_texture_from_norm_view_(norm_texture_from_norm_view), + rawValueToMeters_(raw_value_to_meters) { DVLOG(3) << __func__ << ": size_=" << size_.ToString() << ", norm_texture_from_norm_view_=" - << norm_texture_from_norm_view_.ToString(); - - CHECK_EQ(base::CheckMul(2, size_.width(), size_.height()).ValueOrDie(), - data_->byteLength()); -} - -DOMUint16Array* XRDepthInformation::data( - ExceptionState& exception_state) const { - if (!ValidateFrame(exception_state)) { - return nullptr; - } - - return data_; + << norm_texture_from_norm_view_.ToString() + << ", raw_value_to_meters=" << raw_value_to_meters; } uint32_t XRDepthInformation::width(ExceptionState& exception_state) const { @@ -69,37 +55,13 @@ return size_.height(); } -float XRDepthInformation::getDepth(uint32_t column, - uint32_t row, - ExceptionState& exception_state) const { - DVLOG(3) << __func__ << ": column=" << column << ", row=" << row; - +float XRDepthInformation::rawValueToMeters( + ExceptionState& exception_state) const { if (!ValidateFrame(exception_state)) { return 0.0; } - if (column >= static_cast<size_t>(size_.width())) { - exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError, - kOutOfBoundsAccess); - return 0.0; - } - - if (row >= static_cast<size_t>(size_.height())) { - exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError, - kOutOfBoundsAccess); - return 0.0; - } - - auto checked_index = - base::CheckAdd(column, base::CheckMul(row, size_.width())); - size_t index = checked_index.ValueOrDie(); - - // Data is stored in millimeters, convert to meters when accessing: - float result = data_->Item(index) / 1000.0; - - DVLOG(3) << __func__ << ": index=" << index << ", result=" << result; - - return result; + return rawValueToMeters_; } XRRigidTransform* XRDepthInformation::normTextureFromNormView( @@ -130,7 +92,6 @@ void XRDepthInformation::Trace(Visitor* visitor) const { visitor->Trace(xr_frame_); - visitor->Trace(data_); ScriptWrappable::Trace(visitor); }
diff --git a/third_party/blink/renderer/modules/xr/xr_depth_information.h b/third_party/blink/renderer/modules/xr/xr_depth_information.h index ee5ab0f5..37388c2 100644 --- a/third_party/blink/renderer/modules/xr/xr_depth_information.h +++ b/third_party/blink/renderer/modules/xr/xr_depth_information.h
@@ -6,9 +6,7 @@ #define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_DEPTH_INFORMATION_H_ #include "device/vr/public/mojom/vr_service.mojom-blink-forward.h" -#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h" #include "third_party/blink/renderer/platform/bindings/script_wrappable.h" -#include "third_party/blink/renderer/platform/wtf/forward.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/transform.h" @@ -18,17 +16,22 @@ class XRFrame; class XRRigidTransform; -class XRDepthInformation final : public ScriptWrappable { +class XRDepthInformation : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); - public: + protected: explicit XRDepthInformation(const XRFrame* xr_frame, const gfx::Size& size, const gfx::Transform& norm_texture_from_norm_view, - DOMUint16Array* data); + float raw_value_to_meters); - DOMUint16Array* data(ExceptionState& exception_state) const; + // Helper to validate whether a frame is in a correct state. Should be invoked + // before every member access. If the validation returns `false`, it means the + // validation failed & an exception is going to be thrown and the rest of the + // member access code should not run. + bool ValidateFrame(ExceptionState& exception_state) const; + public: uint32_t width(ExceptionState& exception_state) const; uint32_t height(ExceptionState& exception_state) const; @@ -36,25 +39,17 @@ XRRigidTransform* normTextureFromNormView( ExceptionState& exception_state) const; - float getDepth(uint32_t column, - uint32_t row, - ExceptionState& exception_state) const; + float rawValueToMeters(ExceptionState& exception_state) const; void Trace(Visitor* visitor) const override; - private: + protected: const Member<const XRFrame> xr_frame_; const gfx::Size size_; - const Member<DOMUint16Array> data_; const gfx::Transform norm_texture_from_norm_view_; - - // Helper to validate whether a frame is in a correct state. Should be invoked - // before every member access. If the validation returns `false`, it means the - // validation failed & an exception is going to be thrown and the rest of the - // member access code should not run. - bool ValidateFrame(ExceptionState& exception_state) const; + const float rawValueToMeters_; }; } // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_depth_information.idl b/third_party/blink/renderer/modules/xr/xr_depth_information.idl index 51492921..dea2323 100644 --- a/third_party/blink/renderer/modules/xr/xr_depth_information.idl +++ b/third_party/blink/renderer/modules/xr/xr_depth_information.idl
@@ -7,15 +7,11 @@ Exposed=Window, RuntimeEnabled=WebXRDepth ] interface XRDepthInformation { - [RaisesException, SameObject, MeasureAs=XRDepthInformationDataAttribute] - readonly attribute Uint16Array data; - [RaisesException] readonly attribute unsigned long width; [RaisesException] readonly attribute unsigned long height; [RaisesException, SameObject] readonly attribute XRRigidTransform normTextureFromNormView; - - [RaisesException, MeasureAs=XRDepthInformationGetDepth] - float getDepth(unsigned long column, unsigned long row); + [RaisesException] + readonly attribute float rawValueToMeters; };
diff --git a/third_party/blink/renderer/modules/xr/xr_depth_manager.cc b/third_party/blink/renderer/modules/xr/xr_depth_manager.cc index 9834c72..2b8d314 100644 --- a/third_party/blink/renderer/modules/xr/xr_depth_manager.cc +++ b/third_party/blink/renderer/modules/xr/xr_depth_manager.cc
@@ -5,14 +5,45 @@ #include "third_party/blink/renderer/modules/xr/xr_depth_manager.h" #include "base/trace_event/trace_event.h" -#include "third_party/blink/renderer/modules/xr/xr_depth_information.h" +#include "third_party/blink/renderer/modules/xr/xr_cpu_depth_information.h" #include "third_party/blink/renderer/modules/xr/xr_session.h" +namespace { + +String UsageToString(device::mojom::XRDepthUsage usage) { + switch (usage) { + case device::mojom::XRDepthUsage::kCPUOptimized: + return "cpu-optimized"; + case device::mojom::XRDepthUsage::kGPUOptimized: + return "gpu-optimized"; + } +} + +String DataFormatToString(device::mojom::XRDepthDataFormat data_format) { + switch (data_format) { + case device::mojom::XRDepthDataFormat::kLuminanceAlpha: + return "luminance-alpha"; + case device::mojom::XRDepthDataFormat::kFloat32: + return "float32"; + } +} + +} // namespace + namespace blink { -XRDepthManager::XRDepthManager(base::PassKey<XRSession> pass_key, - XRSession* session) - : session_(session) {} +XRDepthManager::XRDepthManager( + base::PassKey<XRSession> pass_key, + XRSession* session, + const device::mojom::blink::XRDepthConfig& depth_configuration) + : session_(session), + usage_(depth_configuration.depth_usage), + data_format_(depth_configuration.depth_data_format), + usage_str_(UsageToString(usage_)), + data_format_str_(DataFormatToString(data_format_)) { + DVLOG(3) << __func__ << ": usage_=" << usage_ + << ", data_format_=" << data_format_; +} XRDepthManager::~XRDepthManager() = default; @@ -45,7 +76,7 @@ } } -XRDepthInformation* XRDepthManager::GetDepthInformation( +XRCPUDepthInformation* XRDepthManager::GetDepthInformation( const XRFrame* xr_frame) { if (!depth_data_) { return nullptr; @@ -53,9 +84,9 @@ EnsureData(); - return MakeGarbageCollected<XRDepthInformation>( + return MakeGarbageCollected<XRCPUDepthInformation>( xr_frame, depth_data_->size, depth_data_->norm_texture_from_norm_view, - data_); + depth_data_->raw_value_to_meters, data_); } void XRDepthManager::EnsureData() {
diff --git a/third_party/blink/renderer/modules/xr/xr_depth_manager.h b/third_party/blink/renderer/modules/xr/xr_depth_manager.h index 4a37bb6..9fb6168f 100644 --- a/third_party/blink/renderer/modules/xr/xr_depth_manager.h +++ b/third_party/blink/renderer/modules/xr/xr_depth_manager.h
@@ -12,7 +12,7 @@ namespace blink { -class XRDepthInformation; +class XRCPUDepthInformation; class XRFrame; class XRSession; @@ -20,19 +20,30 @@ // out of XRSession. class XRDepthManager : public GarbageCollected<XRDepthManager> { public: - explicit XRDepthManager(base::PassKey<XRSession> pass_key, - XRSession* session); + explicit XRDepthManager( + base::PassKey<XRSession> pass_key, + XRSession* session, + const device::mojom::blink::XRDepthConfig& device_configuration); virtual ~XRDepthManager(); void ProcessDepthInformation(device::mojom::blink::XRDepthDataPtr depth_data); - XRDepthInformation* GetDepthInformation(const XRFrame* xr_frame); + const String& depthUsage() const { return usage_str_; } + const String& depthDataFormat() const { return data_format_str_; } + + XRCPUDepthInformation* GetDepthInformation(const XRFrame* xr_frame); void Trace(Visitor* visitor) const; private: Member<XRSession> session_; + const device::mojom::XRDepthUsage usage_; + const device::mojom::XRDepthDataFormat data_format_; + + const String usage_str_; + const String data_format_str_; + // Current depth data buffer. device::mojom::blink::XRDepthDataUpdatedPtr depth_data_;
diff --git a/third_party/blink/renderer/modules/xr/xr_depth_state_init.idl b/third_party/blink/renderer/modules/xr/xr_depth_state_init.idl new file mode 100644 index 0000000..801ed9b --- /dev/null +++ b/third_party/blink/renderer/modules/xr/xr_depth_state_init.idl
@@ -0,0 +1,18 @@ +// 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. + +enum XRDepthUsage { + "cpu-optimized", + "gpu-optimized" +}; + +enum XRDepthDataFormat { + "luminance-alpha", + "float32", +}; + +dictionary XRDepthStateInit { + required sequence<XRDepthUsage> usagePreference; + required sequence<XRDepthDataFormat> dataFormatPreference; +};
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.cc b/third_party/blink/renderer/modules/xr/xr_frame.cc index 012ccd3..091e33ac 100644 --- a/third_party/blink/renderer/modules/xr/xr_frame.cc +++ b/third_party/blink/renderer/modules/xr/xr_frame.cc
@@ -155,7 +155,7 @@ return light_probe->getLightEstimate(); } -XRDepthInformation* XRFrame::getDepthInformation( +XRCPUDepthInformation* XRFrame::getDepthInformation( XRView* view, ExceptionState& exception_state) const { DVLOG(2) << __func__; @@ -178,7 +178,7 @@ return nullptr; } - return session_->GetDepthInformation(this); + return session_->GetDepthInformation(this, exception_state); } // Return an XRPose that has a transform of basespace_from_space, while
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.h b/third_party/blink/renderer/modules/xr/xr_frame.h index 8224e4d..04e75a2 100644 --- a/third_party/blink/renderer/modules/xr/xr_frame.h +++ b/third_party/blink/renderer/modules/xr/xr_frame.h
@@ -21,7 +21,7 @@ class ExceptionState; class XRAnchorSet; -class XRDepthInformation; +class XRCPUDepthInformation; class XRHitTestResult; class XRHitTestSource; class XRImageTrackingResult; @@ -53,7 +53,7 @@ XRPose* getPose(XRSpace*, XRSpace*, ExceptionState&); XRAnchorSet* trackedAnchors() const; XRLightEstimate* getLightEstimate(XRLightProbe*, ExceptionState&) const; - XRDepthInformation* getDepthInformation( + XRCPUDepthInformation* getDepthInformation( XRView* view, ExceptionState& exception_state) const; XRPlaneSet* detectedPlanes(ExceptionState& exception_state) const;
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.idl b/third_party/blink/renderer/modules/xr/xr_frame.idl index 0bbee6db..c6c65b9 100644 --- a/third_party/blink/renderer/modules/xr/xr_frame.idl +++ b/third_party/blink/renderer/modules/xr/xr_frame.idl
@@ -27,7 +27,7 @@ XRLightEstimate? getLightEstimate(XRLightProbe lightProbe); [RuntimeEnabled=WebXRDepth, RaisesException, MeasureAs=XRFrameGetDepthInformation] - XRDepthInformation getDepthInformation(XRView view); + XRCPUDepthInformation? getDepthInformation(XRView view); [RuntimeEnabled=WebXRImageTracking, RaisesException] FrozenArray<XRImageTrackingResult> getImageTrackingResults();
diff --git a/third_party/blink/renderer/modules/xr/xr_session.cc b/third_party/blink/renderer/modules/xr/xr_session.cc index a320965..1ca67a4 100644 --- a/third_party/blink/renderer/modules/xr/xr_session.cc +++ b/third_party/blink/renderer/modules/xr/xr_session.cc
@@ -105,6 +105,9 @@ const char kEntityTypesNotSpecified[] = "No entityTypes specified: the array cannot be empty!"; +const char kDepthSensingFeatureNotSupported[] = + "Depth sensing feature is not supported by the session."; + const double kDegToRad = M_PI / 180.0; const float kMinDefaultFramebufferScale = 0.1f; @@ -311,6 +314,16 @@ } } +XRDepthManager* XRSession::CreateDepthManagerIfEnabled( + const device::mojom::blink::XRSessionDeviceConfig& device_config) { + if (!device_config.depth_configuration) { + return nullptr; + } + + return MakeGarbageCollected<XRDepthManager>( + base::PassKey<XRSession>{}, this, *device_config.depth_configuration); +} + XRSession::XRSession( XRSystem* xr, mojo::PendingReceiver<device::mojom::blink::XRSessionClient> @@ -329,10 +342,7 @@ plane_manager_( MakeGarbageCollected<XRPlaneManager>(base::PassKey<XRSession>{}, this)), - depth_manager_( - MakeGarbageCollected<XRDepthManager>(base::PassKey<XRSession>{}, - this)), - + depth_manager_(CreateDepthManagerIfEnabled(*device_config)), input_sources_(MakeGarbageCollected<XRInputSourceArray>()), client_receiver_(this, xr->GetExecutionContext()), input_receiver_(this, xr->GetExecutionContext()), @@ -478,6 +488,26 @@ MaybeRequestFrame(); } +const String& XRSession::depthUsage(ExceptionState& exception_state) { + if (!depth_manager_) { + exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, + kDepthSensingFeatureNotSupported); + return g_empty_string; + } + + return depth_manager_->depthUsage(); +} + +const String& XRSession::depthDataFormat(ExceptionState& exception_state) { + if (!depth_manager_) { + exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, + kDepthSensingFeatureNotSupported); + return g_empty_string; + } + + return depth_manager_->depthDataFormat(); +} + void XRSession::UpdateEyeParameters( const device::mojom::blink::VREyeParametersPtr& left_eye, const device::mojom::blink::VREyeParametersPtr& right_eye) { @@ -1218,8 +1248,15 @@ } } -XRDepthInformation* XRSession::GetDepthInformation( - const XRFrame* xr_frame) const { +XRCPUDepthInformation* XRSession::GetDepthInformation( + const XRFrame* xr_frame, + ExceptionState& exception_state) const { + if (!depth_manager_) { + exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, + kDepthSensingFeatureNotSupported); + return nullptr; + } + return depth_manager_->GetDepthInformation(xr_frame); } @@ -1731,7 +1768,12 @@ frame_data->detected_planes_data.get(), timestamp); ProcessAnchorsData(frame_data->anchors_data.get(), timestamp); ProcessHitTestData(frame_data->hit_test_subscription_results.get()); - depth_manager_->ProcessDepthInformation(std::move(frame_data->depth_data)); + + if (depth_manager_) { + depth_manager_->ProcessDepthInformation( + std::move(frame_data->depth_data)); + } + ProcessTrackedImagesData(frame_data->tracked_images.get()); const device::mojom::blink::XRLightEstimationData* light_data = @@ -1743,7 +1785,11 @@ plane_manager_->ProcessPlaneInformation(nullptr, timestamp); ProcessAnchorsData(nullptr, timestamp); ProcessHitTestData(nullptr); - depth_manager_->ProcessDepthInformation(nullptr); + + if (depth_manager_) { + depth_manager_->ProcessDepthInformation(nullptr); + } + ProcessTrackedImagesData(nullptr); if (world_light_probe_) {
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h index 8ef610b..b9e2a5fc 100644 --- a/third_party/blink/renderer/modules/xr/xr_session.h +++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -42,7 +42,7 @@ class XRAnchor; class XRAnchorSet; class XRCanvasInputProvider; -class XRDepthInformation; +class XRCPUDepthInformation; class XRDepthManager; class XRDOMOverlayState; class XRHitTestOptionsInit; @@ -149,6 +149,10 @@ void updateRenderState(XRRenderStateInit* render_state_init, ExceptionState& exception_state); + + const String& depthUsage(ExceptionState& exception_state); + const String& depthDataFormat(ExceptionState& exception_state); + ScriptPromise requestReferenceSpace(ScriptState* script_state, const String& type, ExceptionState&); @@ -337,7 +341,9 @@ base::Optional<TransformationMatrix> GetMojoFrom( device::mojom::blink::XRReferenceSpaceType space_type) const; - XRDepthInformation* GetDepthInformation(const XRFrame* xr_frame) const; + XRCPUDepthInformation* GetDepthInformation( + const XRFrame* xr_frame, + ExceptionState& exception_state) const; XRPlaneSet* GetDetectedPlanes() const; @@ -450,6 +456,11 @@ void ExecuteVideoFrameCallbacks(double timestamp); + // Helper, creates an instance of depth manager if depth sensing API is + // enabled in the session configuration. + XRDepthManager* CreateDepthManagerIfEnabled( + const device::mojom::blink::XRSessionDeviceConfig& device_config); + const Member<XRSystem> xr_; const device::mojom::blink::XRSessionMode mode_; const bool environment_integration_;
diff --git a/third_party/blink/renderer/modules/xr/xr_session.idl b/third_party/blink/renderer/modules/xr/xr_session.idl index bc44f22..9dc54c9 100644 --- a/third_party/blink/renderer/modules/xr/xr_session.idl +++ b/third_party/blink/renderer/modules/xr/xr_session.idl
@@ -81,4 +81,9 @@ [RuntimeEnabled=WebXRImageTracking, CallWith=ScriptState, RaisesException] Promise<FrozenArray<XRImageTrackingScore>> getTrackedImageScores(); + + [RuntimeEnabled=WebXRDepth, RaisesException] + readonly attribute XRDepthUsage? depthUsage; + [RuntimeEnabled=WebXRDepth, RaisesException] + readonly attribute XRDepthDataFormat? depthDataFormat; };
diff --git a/third_party/blink/renderer/modules/xr/xr_session_init.idl b/third_party/blink/renderer/modules/xr/xr_session_init.idl index 2e1b5d7..24076b8 100644 --- a/third_party/blink/renderer/modules/xr/xr_session_init.idl +++ b/third_party/blink/renderer/modules/xr/xr_session_init.idl
@@ -8,4 +8,5 @@ sequence<any> optionalFeatures; XRDOMOverlayInit domOverlay; [RuntimeEnabled=WebXRImageTracking] sequence<XRTrackedImageInit> trackedImages; + [RuntimeEnabled=WebXRDepth] XRDepthStateInit depthSensing; };
diff --git a/third_party/blink/renderer/modules/xr/xr_system.cc b/third_party/blink/renderer/modules/xr/xr_system.cc index 0716ffd3..2dd32b00 100644 --- a/third_party/blink/renderer/modules/xr/xr_system.cc +++ b/third_party/blink/renderer/modules/xr/xr_system.cc
@@ -6,6 +6,7 @@ #include <utility> +#include "device/vr/public/mojom/vr_service.mojom-blink.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "third_party/blink/public/common/browser_interface_broker_proxy.h" #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h" @@ -29,6 +30,7 @@ #include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/modules/event_modules.h" #include "third_party/blink/renderer/modules/event_target_modules.h" +#include "third_party/blink/renderer/modules/xr/xr_depth_state_init.h" #include "third_party/blink/renderer/modules/xr/xr_frame_provider.h" #include "third_party/blink/renderer/modules/xr/xr_session.h" #include "third_party/blink/renderer/modules/xr/xr_session_viewport_scaler.h" @@ -69,6 +71,10 @@ const char kTrackedImageWidthInvalid[] = "trackedImages[%d].widthInMeters invalid, must be a positive number."; +const char kDepthSensingConfigurationNotSupported[] = + "The provided preferences depth sensing usage and format are not " + "supported, unable to create the session."; + constexpr device::mojom::XRSessionFeature kDefaultImmersiveVrFeatures[] = { device::mojom::XRSessionFeature::REF_SPACE_VIEWER, device::mojom::XRSessionFeature::REF_SPACE_LOCAL, @@ -113,6 +119,48 @@ return ""; } +device::mojom::XRDepthUsage ParseDepthUsage(const String& usage) { + if (usage == "cpu-optimized") { + return device::mojom::XRDepthUsage::kCPUOptimized; + } else if (usage == "gpu-optimized") { + return device::mojom::XRDepthUsage::kGPUOptimized; + } + + NOTREACHED() << "Only strings in the enum are allowed by IDL"; + return device::mojom::XRDepthUsage::kCPUOptimized; +} + +Vector<device::mojom::XRDepthUsage> ParseDepthUsages( + const Vector<String>& usages) { + Vector<device::mojom::XRDepthUsage> result; + + std::transform(usages.begin(), usages.end(), std::back_inserter(result), + ParseDepthUsage); + + return result; +} + +device::mojom::XRDepthDataFormat ParseDepthFormat(const String& format) { + if (format == "luminance-alpha") { + return device::mojom::XRDepthDataFormat::kLuminanceAlpha; + } else if (format == "float32") { + return device::mojom::XRDepthDataFormat::kFloat32; + } + + NOTREACHED() << "Only strings in the enum are allowed by IDL"; + return device::mojom::XRDepthDataFormat::kLuminanceAlpha; +} + +Vector<device::mojom::XRDepthDataFormat> ParseDepthFormats( + const Vector<String>& formats) { + Vector<device::mojom::XRDepthDataFormat> result; + + std::transform(formats.begin(), formats.end(), std::back_inserter(result), + ParseDepthFormat); + + return result; +} + // Converts the given string to an XRSessionFeature. If the string is // unrecognized, returns nullopt. Based on the spec: // https://immersive-web.github.io/webxr/#feature-name @@ -202,9 +250,17 @@ case device::mojom::XRSessionFeature::LIGHT_ESTIMATION: case device::mojom::XRSessionFeature::CAMERA_ACCESS: case device::mojom::XRSessionFeature::PLANE_DETECTION: + // Fallthrough - light estimation, camera access, and plane detection are + // all valid only for immersive AR mode for now. + return mode == device::mojom::blink::XRSessionMode::kImmersiveAr; case device::mojom::XRSessionFeature::DEPTH: - // Fallthrough - light estimation, camera access, plane detection and - // depth APIs are all valid only for immersive AR mode for now. + if (!session_init->hasDepthSensing()) { + execution_context->AddConsoleMessage( + MakeGarbageCollected<ConsoleMessage>( + mojom::blink::ConsoleMessageSource::kJavaScript, error_level, + "Must provide a depthSensing dictionary in XRSessionInit")); + return false; + } return mode == device::mojom::blink::XRSessionMode::kImmersiveAr; } } @@ -800,6 +856,15 @@ device::mojom::blink::XRTrackedImage::New(); *session_options->tracked_images[i] = query.TrackedImages()[i]; } + + if (query.HasFeature(device::mojom::XRSessionFeature::DEPTH)) { + session_options->depth_options = + device::mojom::blink::XRDepthOptions::New(); + session_options->depth_options->usage_preferences = query.PreferredUsage(); + session_options->depth_options->data_format_preferences = + query.PreferredFormat(); + } + return session_options; } @@ -1369,6 +1434,36 @@ query->SetTrackedImages(images); } + if (query->HasFeature(device::mojom::XRSessionFeature::DEPTH)) { + // Prerequisites were checked by IsFeatureValidForMode and IDL. + DCHECK(session_init); + DCHECK(session_init->hasDepthSensing()); + DCHECK(session_init->depthSensing()->hasUsagePreference()) + << "required in IDL"; + DCHECK(session_init->depthSensing()->hasDataFormatPreference()) + << "required in IDL"; + + Vector<device::mojom::XRDepthUsage> preferred_usage = + ParseDepthUsages(session_init->depthSensing()->usagePreference()); + Vector<device::mojom::XRDepthDataFormat> preferred_format = + ParseDepthFormats(session_init->depthSensing()->dataFormatPreference()); + + // If the depth API is required and either preferred usages or preferred + // formats are empty, we already know that the session creation will fail + // (as we won't be able to pick a supported usage & format combination), so + // let's fail it already: + if (query->RequiredFeatures().Contains( + device::mojom::XRSessionFeature::DEPTH) && + (preferred_usage.IsEmpty() || preferred_format.IsEmpty())) { + query->RejectWithDOMException(DOMExceptionCode::kNotSupportedError, + kDepthSensingConfigurationNotSupported, + &exception_state); + return promise; + } + + query->SetDepthSensingConfiguration(preferred_usage, preferred_format); + } + // The various session request methods may have other checks that would reject // before needing to create the vr service, so we don't try to create it here. switch (session_mode) {
diff --git a/third_party/blink/renderer/modules/xr/xr_system.h b/third_party/blink/renderer/modules/xr/xr_system.h index 4640a3b..fb649c8 100644 --- a/third_party/blink/renderer/modules/xr/xr_system.h +++ b/third_party/blink/renderer/modules/xr/xr_system.h
@@ -228,6 +228,21 @@ return tracked_images_; } + void SetDepthSensingConfiguration( + const Vector<device::mojom::XRDepthUsage>& preferred_usage, + const Vector<device::mojom::XRDepthDataFormat>& preferred_format) { + preferred_usage_ = preferred_usage; + preferred_format_ = preferred_format; + } + + const Vector<device::mojom::XRDepthUsage>& PreferredUsage() const { + return preferred_usage_; + } + + const Vector<device::mojom::XRDepthDataFormat>& PreferredFormat() const { + return preferred_format_; + } + virtual void Trace(Visitor*) const; private: @@ -253,6 +268,9 @@ Vector<device::mojom::blink::XRTrackedImage> tracked_images_; + Vector<device::mojom::XRDepthUsage> preferred_usage_; + Vector<device::mojom::XRDepthDataFormat> preferred_format_; + DISALLOW_COPY_AND_ASSIGN(PendingRequestSessionQuery); };
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc b/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc index 111a122a..56d987b 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc +++ b/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc
@@ -157,6 +157,12 @@ return texture; } +XRWebGLDepthInformation* XRWebGLBinding::getDepthInformation( + XRView* view, + ExceptionState& exception_state) { + return nullptr; +} + void XRWebGLBinding::Trace(Visitor* visitor) const { visitor->Trace(session_); visitor->Trace(webgl_context_);
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_binding.h b/third_party/blink/renderer/modules/xr/xr_webgl_binding.h index 8d2bdce..55f88c3 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_binding.h +++ b/third_party/blink/renderer/modules/xr/xr_webgl_binding.h
@@ -19,6 +19,7 @@ class XRLightProbe; class XRSession; class XRView; +class XRWebGLDepthInformation; class XRWebGLBinding final : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); @@ -36,6 +37,9 @@ WebGLTexture* getReflectionCubeMap(XRLightProbe*, ExceptionState&); WebGLTexture* getCameraImage(XRFrame*, XRView*); + XRWebGLDepthInformation* getDepthInformation(XRView* view, + ExceptionState& exception_state); + void Trace(Visitor*) const override; private:
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_binding.idl b/third_party/blink/renderer/modules/xr/xr_webgl_binding.idl index 4b350b22..a2b0851b 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_binding.idl +++ b/third_party/blink/renderer/modules/xr/xr_webgl_binding.idl
@@ -14,4 +14,7 @@ WebGLTexture? getReflectionCubeMap(XRLightProbe lightProbe); [RuntimeEnabled=WebXRCameraAccess] WebGLTexture? getCameraImage(XRFrame frame, XRView view); + + [RuntimeEnabled=WebXRDepth, RaisesException, MeasureAs=XRWebGLBindingGetDepthInformation] + XRWebGLDepthInformation? getDepthInformation(XRView view); };
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_depth_information.cc b/third_party/blink/renderer/modules/xr/xr_webgl_depth_information.cc new file mode 100644 index 0000000..afc2197 --- /dev/null +++ b/third_party/blink/renderer/modules/xr/xr_webgl_depth_information.cc
@@ -0,0 +1,14 @@ +// 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 "third_party/blink/renderer/modules/xr/xr_webgl_depth_information.h" + +namespace blink { + +WebGLTexture* XRWebGLDepthInformation::texture( + ExceptionState& exception_state) { + return nullptr; +} + +} // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_depth_information.h b/third_party/blink/renderer/modules/xr/xr_webgl_depth_information.h new file mode 100644 index 0000000..4ed0ed6 --- /dev/null +++ b/third_party/blink/renderer/modules/xr/xr_webgl_depth_information.h
@@ -0,0 +1,24 @@ +// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_WEBGL_DEPTH_INFORMATION_H_ +#define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_WEBGL_DEPTH_INFORMATION_H_ + +#include "third_party/blink/renderer/modules/xr/xr_depth_information.h" + +namespace blink { + +class ExceptionState; +class WebGLTexture; + +class XRWebGLDepthInformation final : public XRDepthInformation { + DEFINE_WRAPPERTYPEINFO(); + + public: + WebGLTexture* texture(ExceptionState& exception_state); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_WEBGL_DEPTH_INFORMATION_H_
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_depth_information.idl b/third_party/blink/renderer/modules/xr/xr_webgl_depth_information.idl new file mode 100644 index 0000000..e86904769 --- /dev/null +++ b/third_party/blink/renderer/modules/xr/xr_webgl_depth_information.idl
@@ -0,0 +1,13 @@ +// 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. + +[ + SecureContext, + Exposed=Window, + RuntimeEnabled=WebXRDepth +] interface XRWebGLDepthInformation : XRDepthInformation { + [RaisesException, SameObject, MeasureAs=XRWebGLDepthInformationTextureAttribute] + readonly attribute WebGLTexture texture; +}; +
diff --git a/third_party/blink/renderer/platform/heap/BUILD.gn b/third_party/blink/renderer/platform/heap/BUILD.gn index f7e78ee..58652b5 100644 --- a/third_party/blink/renderer/platform/heap/BUILD.gn +++ b/third_party/blink/renderer/platform/heap/BUILD.gn
@@ -100,6 +100,7 @@ if (enable_blink_heap_use_v8_oilpan) { sources += [ "v8_wrapper/blink_gc.h", + "v8_wrapper/blink_gc_memory_dump_provider.cc", "v8_wrapper/blink_gc_memory_dump_provider.h", "v8_wrapper/collection_support/heap_hash_table_backing.h", "v8_wrapper/collection_support/heap_vector_backing.h",
diff --git a/third_party/blink/renderer/platform/heap/v8_wrapper/blink_gc_memory_dump_provider.cc b/third_party/blink/renderer/platform/heap/v8_wrapper/blink_gc_memory_dump_provider.cc new file mode 100644 index 0000000..1f08954 --- /dev/null +++ b/third_party/blink/renderer/platform/heap/v8_wrapper/blink_gc_memory_dump_provider.cc
@@ -0,0 +1,56 @@ +// 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 "third_party/blink/renderer/platform/heap/v8_wrapper/blink_gc_memory_dump_provider.h" + +#include <inttypes.h> + +#include "base/strings/stringprintf.h" +#include "base/trace_event/memory_dump_manager.h" + +namespace blink { +namespace { + +constexpr const char* HeapTypeString( + BlinkGCMemoryDumpProvider::HeapType heap_type) { + switch (heap_type) { + case BlinkGCMemoryDumpProvider::HeapType::kBlinkMainThread: + return "main"; + case BlinkGCMemoryDumpProvider::HeapType::kBlinkWorkerThread: + return "workers"; + } +} + +} // namespace + +BlinkGCMemoryDumpProvider::BlinkGCMemoryDumpProvider( + ThreadState* thread_state, + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + BlinkGCMemoryDumpProvider::HeapType heap_type) + : thread_state_(thread_state), + heap_type_(heap_type), + dump_base_name_( + "blink_gc/" + std::string(HeapTypeString(heap_type_)) + "/heap" + + (heap_type_ == HeapType::kBlinkWorkerThread + ? "/" + base::StringPrintf( + "worker_0x%" PRIXPTR, + reinterpret_cast<uintptr_t>(thread_state_)) + : "")) { + base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( + this, "BlinkGC", task_runner); +} + +BlinkGCMemoryDumpProvider::~BlinkGCMemoryDumpProvider() { + base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( + this); +} + +bool BlinkGCMemoryDumpProvider::OnMemoryDump( + const base::trace_event::MemoryDumpArgs& args, + base::trace_event::ProcessMemoryDump* process_memory_dump) { + // TODO(chromium:1056170): Provide implementation. + return false; +} + +} // namespace blink
diff --git a/third_party/blink/renderer/platform/heap/v8_wrapper/blink_gc_memory_dump_provider.h b/third_party/blink/renderer/platform/heap/v8_wrapper/blink_gc_memory_dump_provider.h index 8773bd7..0bb476f 100644 --- a/third_party/blink/renderer/platform/heap/v8_wrapper/blink_gc_memory_dump_provider.h +++ b/third_party/blink/renderer/platform/heap/v8_wrapper/blink_gc_memory_dump_provider.h
@@ -5,6 +5,40 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_V8_WRAPPER_BLINK_GC_MEMORY_DUMP_PROVIDER_H_ #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_V8_WRAPPER_BLINK_GC_MEMORY_DUMP_PROVIDER_H_ -// TODO(chromium:1056170): Implement wrapper. +#include "base/trace_event/memory_dump_provider.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace blink { + +class ThreadState; + +class PLATFORM_EXPORT BlinkGCMemoryDumpProvider final + : public base::trace_event::MemoryDumpProvider { + USING_FAST_MALLOC(BlinkGCMemoryDumpProvider); + + public: + enum class HeapType { kBlinkMainThread, kBlinkWorkerThread }; + + ~BlinkGCMemoryDumpProvider() final; + BlinkGCMemoryDumpProvider(ThreadState*, + scoped_refptr<base::SingleThreadTaskRunner>, + HeapType); + + // MemoryDumpProvider implementation. + bool OnMemoryDump(const base::trace_event::MemoryDumpArgs&, + base::trace_event::ProcessMemoryDump*) final; + + private: + ThreadState* const thread_state_; + const HeapType heap_type_; + const std::string dump_base_name_; +}; + +} // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_V8_WRAPPER_BLINK_GC_MEMORY_DUMP_PROVIDER_H_
diff --git a/third_party/blink/renderer/platform/heap/v8_wrapper/heap.h b/third_party/blink/renderer/platform/heap/v8_wrapper/heap.h index b3ee5ed..e81eb2f4 100644 --- a/third_party/blink/renderer/platform/heap/v8_wrapper/heap.h +++ b/third_party/blink/renderer/platform/heap/v8_wrapper/heap.h
@@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_V8_WRAPPER_HEAP_H_ #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_V8_WRAPPER_HEAP_H_ +#include "third_party/blink/renderer/platform/heap/v8_wrapper/process_heap.h" #include "third_party/blink/renderer/platform/heap/v8_wrapper/thread_state.h" #include "v8/include/cppgc/allocation.h" #include "v8/include/cppgc/garbage-collected.h"
diff --git a/third_party/blink/renderer/platform/heap/v8_wrapper/process_heap.h b/third_party/blink/renderer/platform/heap/v8_wrapper/process_heap.h index 254f163b..a0876e35 100644 --- a/third_party/blink/renderer/platform/heap/v8_wrapper/process_heap.h +++ b/third_party/blink/renderer/platform/heap/v8_wrapper/process_heap.h
@@ -5,6 +5,23 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_V8_WRAPPER_PROCESS_HEAP_H_ #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_V8_WRAPPER_PROCESS_HEAP_H_ -// TODO(chromium:1056170): Implement wrapper. +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" + +namespace blink { + +// TODO(1056170): Implement wrapper. +class PLATFORM_EXPORT ProcessHeap { + STATIC_ONLY(ProcessHeap); + + public: + static void Init() {} + + static size_t TotalAllocatedObjectSize() { return 0; } + + static size_t TotalAllocatedSpace() { return 0; } +}; + +} // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_V8_WRAPPER_PROCESS_HEAP_H_
diff --git a/third_party/blink/renderer/platform/heap/v8_wrapper/thread_state.h b/third_party/blink/renderer/platform/heap/v8_wrapper/thread_state.h index 640b6eca..51f5bd3d 100644 --- a/third_party/blink/renderer/platform/heap/v8_wrapper/thread_state.h +++ b/third_party/blink/renderer/platform/heap/v8_wrapper/thread_state.h
@@ -98,6 +98,9 @@ // TODO(1056170): Implement, if necessary for testing. } + bool IsMainThread() const { return this == MainThreadState(); } + bool IsCreationThread() const { return thread_id_ == CurrentThread(); } + private: // Main-thread ThreadState avoids TLS completely by using a regular global. // The object is manually managed and should not rely on global ctor/dtor. @@ -110,9 +113,6 @@ explicit ThreadState(v8::CppHeap&); ~ThreadState(); - bool IsMainThread() const { return this == MainThreadState(); } - bool IsCreationThread() const { return thread_id_ == CurrentThread(); } - // Handle is the most frequently accessed field as it is required for // MakeGarbageCollected(). cppgc::AllocationHandle& allocation_handle_;
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc index 123ad3c6..06050bc 100644 --- a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc +++ b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc
@@ -11,11 +11,18 @@ #include "base/bit_cast.h" #include "base/strings/stringprintf.h" +#include "media/media_buildflags.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h" #include "third_party/blink/renderer/platform/wtf/shared_buffer.h" #include "third_party/libavif/src/include/avif/avif.h" +// If the AV1 decoder library supports the bit depth 12, define +// HAVE_AVIF_BIT_DEPTH_12_SUPPORT. +#if BUILDFLAG(ENABLE_DAV1D_DECODER) +#define HAVE_AVIF_BIT_DEPTH_12_SUPPORT +#endif + #define FIXME_SUPPORT_ICC_PROFILE_NO_TRANSFORM 0 #define FIXME_SUPPORT_ICC_PROFILE_TRANSFORM 0 #define FIXME_DISTINGUISH_LOSSY_OR_LOSSLESS 0 @@ -726,6 +733,10 @@ const IntSize& expected_uv_size, SkColorType color_type = kGray_8_SkColorType, int bit_depth = 8) { +#if !defined(HAVE_AVIF_BIT_DEPTH_12_SUPPORT) + if (bit_depth == 12) + return; +#endif SCOPED_TRACE(base::StringPrintf("file_name=%s, color_type=%d", file_name, int{color_type})); @@ -769,6 +780,7 @@ &CreateAVIFDecoder, "/images/resources/avif/star-animated-10bpc-with-alpha.avif", 5u, kAnimationLoopInfinite); +#if defined(HAVE_AVIF_BIT_DEPTH_12_SUPPORT) TestByteByByteDecode(&CreateAVIFDecoder, "/images/resources/avif/star-animated-12bpc.avif", 5u, kAnimationLoopInfinite); @@ -776,6 +788,7 @@ &CreateAVIFDecoder, "/images/resources/avif/star-animated-12bpc-with-alpha.avif", 5u, kAnimationLoopInfinite); +#endif // TODO(ryoh): Add animated avif files with EditListBox. } @@ -822,10 +835,12 @@ &CreateAVIFDecoder, "/images/resources/avif/red-at-12-oclock-with-color-profile-10bpc.avif", 1, kAnimationNone); +#if defined(HAVE_AVIF_BIT_DEPTH_12_SUPPORT) TestByteByByteDecode( &CreateAVIFDecoder, "/images/resources/avif/red-at-12-oclock-with-color-profile-12bpc.avif", 1, kAnimationNone); +#endif } TEST(StaticAVIFTests, YUV) { @@ -880,6 +895,10 @@ TEST_P(StaticAVIFColorTests, InspectImage) { const StaticColorCheckParam& param = GetParam(); +#if !defined(HAVE_AVIF_BIT_DEPTH_12_SUPPORT) + if (param.bit_depth == 12) + return; +#endif // TODO(ryoh): Add tests with ImageDecoder::kHighBitDepthToHalfFloat std::unique_ptr<ImageDecoder> decoder = CreateAVIFDecoderWithOptions( param.alpha_option, ImageDecoder::kDefaultBitDepth, param.color_behavior,
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index aae587af..22bb0c5a 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -139,7 +139,7 @@ }, { name: "AddEventListenerAbortSignal", - status: "experimental", + status: "stable", }, { name: "AddressSpace",
diff --git a/third_party/blink/renderer/platform/wtf/allocator/allocator.h b/third_party/blink/renderer/platform/wtf/allocator/allocator.h index 4d25949c..13f4753 100644 --- a/third_party/blink/renderer/platform/wtf/allocator/allocator.h +++ b/third_party/blink/renderer/platform/wtf/allocator/allocator.h
@@ -28,10 +28,7 @@ // non-garbage-collected objects to avoid unintended allocations. // // STACK_ALLOCATED(): Use if the object is only stack allocated. -// Garbage-collected objects should be in Members but you do not need the -// trace method as they are on the stack. (Down the line these might turn -// in to raw pointers, but for now Members indicate that we have thought -// about them and explicitly taken care of them.) +// Garbage-collected objects should be in raw pointers. // // DISALLOW_NEW(): Cannot be allocated with new operators but can be a // part of object, a value object in collections or stack allocated. If it has
diff --git a/third_party/blink/web_tests/MSANExpectations b/third_party/blink/web_tests/MSANExpectations index 39b5ade6..c8ee7e47d 100644 --- a/third_party/blink/web_tests/MSANExpectations +++ b/third_party/blink/web_tests/MSANExpectations
@@ -149,7 +149,7 @@ crbug.com/1095518 [ Linux ] crbug.com/840659 external/wpt/webrtc/simulcast/h264.https.html [ Crash ] # Sheriff 2020-10-05 -crbug.com/1134580 [ Linux ] virtual/eye-dropper/color-picker-show-eye-dropper.html [ Pass Timeout ] +crbug.com/1134580 [ Linux ] virtual/eye-dropper/http/tests/eye-dropper/color-picker-show-eye-dropper.html [ Pass Timeout ] # Sheriff 2020-11-25 crbug.com/1152088 [ Linux ] fast/dom/cssTarget-crash.html [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests index 8b7d95d6..f4ed037 100644 --- a/third_party/blink/web_tests/NeverFixTests +++ b/third_party/blink/web_tests/NeverFixTests
@@ -2254,3 +2254,9 @@ crbug.com/626703 external/wpt/uievents/order-of-events/mouse-events/mouseevents-mousemove-manual.htm [ Skip ] crbug.com/626703 external/wpt/uievents/order-of-events/focus-events/legacy-manual.html [ Skip ] crbug.com/626703 external/wpt/pointerevents/pointerevent_pointerId_scope-manual.html [ Skip ] + +# Requires EyeDropper feature to pass. Remove from virtual tests and this file +# once that feature is on by default. +http/tests/eye-dropper/color-picker-show-eye-dropper.html [ Skip ] +virtual/eye-dropper/http/tests/eye-dropper/color-picker-show-eye-dropper.html [ Pass ] +virtual/dark-color-scheme/http/tests/eye-dropper/color-picker-show-eye-dropper.html [ Pass ]
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests index cad854cb..bf6ad01 100644 --- a/third_party/blink/web_tests/SlowTests +++ b/third_party/blink/web_tests/SlowTests
@@ -525,7 +525,7 @@ crbug.com/1104091 [ Linux ] webcodecs/videoframe-imagebitmap.html [ Slow ] crbug.com/1093478 external/wpt/quirks/unitless-length/limited-quirks.html [ Slow ] crbug.com/1133836 external/wpt/scroll-to-text-fragment/redirects.html [ Slow ] -crbug.com/1134580 virtual/eye-dropper/color-picker-show-eye-dropper.html [ Slow ] +crbug.com/1134580 virtual/eye-dropper/http/tests/eye-dropper/color-picker-show-eye-dropper.html [ Slow ] crbug.com/1145716 fast/forms/calendar-picker/datetimelocal-picker-open-to-focused-field.html [ Slow ] # These began to hit timeouts when moving from Windows-10-15063 to Windows-10-18363
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites index 4a8c14c6..05fd9cb 100644 --- a/third_party/blink/web_tests/VirtualTestSuites +++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -552,7 +552,9 @@ }, { "prefix": "eye-dropper", - "bases": ["fast/forms/color-scheme/color"], + "bases": [ + "fast/forms/color-scheme/color", + "http/tests/eye-dropper"], "args": ["--enable-features=EyeDropper"] }, { @@ -582,7 +584,7 @@ "prefix": "dark-color-scheme", "bases": ["external/wpt/css/css-color-adjust/rendering/dark-color-scheme", "fast/forms/color-scheme", - "virtual/eye-dropper"], + "http/tests/eye-dropper"], "args": ["--force-dark-mode", "--enable-blink-features=CSSColorScheme,CSSColorSchemeUARendering", "--enable-features=EyeDropper"]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json index 7f8befb..ddf35be 100644 --- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json +++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -64640,6 +64640,71 @@ {} ] ], + "flex-minimum-height-flex-items-026.html": [ + "9f46c953c4a8396c7dcb58012b16235c0b9e5377", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], + "flex-minimum-height-flex-items-027.html": [ + "052ff6a5bd89c56b51d76a1782ac87d1a9d1e2c3", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], + "flex-minimum-height-flex-items-028.html": [ + "45cf76837d345b8e84daec72462f8ae8c42f19b5", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], + "flex-minimum-height-flex-items-029.html": [ + "62b77b92fc91f206d96b6208d65bfb3bd524088f", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], + "flex-minimum-height-flex-items-030.html": [ + "a3dc56e59e433e64a6f024c5d9ea8264cb658210", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], "flex-minimum-width-flex-items-001.xht": [ "cd18483ba414160c46e30bc282dec0c2fcd2f418", [ @@ -64809,6 +64874,32 @@ {} ] ], + "flex-minimum-width-flex-items-015.html": [ + "d4020fe75e3ee1e52b07576132f8db2f880ebfcd", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], + "flex-minimum-width-flex-items-016.html": [ + "a3f4eb4a85cebf937e5568f7431358af71e388e3", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], "flex-order.html": [ "be24b2817e232b7ad7fc936b7b22787591d3d9db", [ @@ -66083,6 +66174,19 @@ {} ] ], + "flexbox-definite-sizes-006.html": [ + "4b5aaf8dc443ed986dafc334c79dd3d7627698f8", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], "flexbox-dyn-resize-001.html": [ "d64c4bdf28ecb783af4f342d515dcf63134602c6", [ @@ -76403,6 +76507,84 @@ {} ] ], + "replaced-alignment-with-aspect-ratio-004.html": [ + "437b379332e90ab7ae4a48c17d3f9c7730ba3b98", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], + "replaced-alignment-with-aspect-ratio-005.html": [ + "b14c45d0c2d9cb93577b71df90d430aff37734d6", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], + "replaced-alignment-with-aspect-ratio-006.html": [ + "ed14e36057948ad5c90f702b6181adba8a72cca7", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], + "replaced-alignment-with-aspect-ratio-007.html": [ + "0c841c7654c237dd5cb17f5790cd738a79caef24", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], + "replaced-alignment-with-aspect-ratio-008.html": [ + "9227332851a46f597fbe8bb9d12f5d0b45975cfa", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], + "replaced-alignment-with-aspect-ratio-009.html": [ + "ff721b8f19b4c2c781fd0c3c69d3e44e541d63a8", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], "self-baseline": { "grid-self-baseline-001.html": [ "2473bb5d4fc8adc351af441bf39cffcacc076273", @@ -163483,6 +163665,49 @@ ] } }, + "interactive-elements": { + "the-popup-element": { + "popup-hidden-display.tentative.html": [ + "c80aecf1d30a3686584bd0bea5988a0ddcc32e19", + [ + null, + [ + [ + "/html/semantics/interactive-elements/the-popup-element/popup-hidden-display-ref.tentative.html", + "==" + ] + ], + {} + ] + ], + "popup-inside-display-none.tentative.html": [ + "824e7005e9b4fc2037c1273b0ad526441a7acb0c", + [ + null, + [ + [ + "/html/semantics/interactive-elements/the-popup-element/popup-hidden-display-ref.tentative.html", + "==" + ] + ], + {} + ] + ], + "popup-open-display.tentative.html": [ + "0f6bd930212e2e9dd210c9af5814af5072706588", + [ + null, + [ + [ + "/html/semantics/interactive-elements/the-popup-element/popup-open-display-ref.tentative.html", + "==" + ] + ], + {} + ] + ] + } + }, "links": { "linktypes": { "alternate-css.html": [ @@ -176355,10 +176580,6 @@ ] }, "sandbox": { - "meta-element.sub-expected.txt": [ - "329b19aeb83a744ca44e05ddf619002ffbadf33e", - [] - ], "service-worker-sandbox.https-expected.txt": [ "6a5fa36ef548243fa1317b0a99f9c0bb5a79ae8e", [] @@ -195917,6 +196138,14 @@ "25b76c3c6f216793a36b1f29287dafd993898c67", [] ], + "25x50-green.png": [ + "6ab02fce0fa9b708880d4dbb2dc2b927fd30f59b", + [] + ], + "50x50-green.png": [ + "6c1406b7dfd7c59f413a183c8896b8c2b3395bf5", + [] + ], "style-change.js": [ "766d140d2b239bcc0c11e92481c5ff302d12db5c", [] @@ -215016,7 +215245,7 @@ [] ], "test-common.css": [ - "dcbf4e1e0240bf622edcea2b7f68f58d89c5ff82", + "218dfcb77060426b0588dd5b28979a4a038befd4", [] ] }, @@ -233237,6 +233466,16 @@ [] ] } + }, + "the-popup-element": { + "popup-hidden-display-ref.tentative.html": [ + "7f4ced38c511785382e7304004a6fac5fc9ff581", + [] + ], + "popup-open-display-ref.tentative.html": [ + "1b063444c3b9f0da6cf5ea4c0262d83d6dff0834", + [] + ] } }, "interfaces.js": [ @@ -237796,7 +238035,7 @@ ] }, "lint.ignore": [ - "0c12d66c04586cf2a807da9b25100fd925e01fa4", + "e296376fb69cdef8d293d3b13fb620eb20016553", [] ], "loading": { @@ -244323,7 +244562,7 @@ [] ], "testharness.js": [ - "3623aa4d57e6bdab1fcab1b235646331381df31e", + "adfb692ccd9bf202cc5642558c588ab168af7906", [] ], "testharness.js.headers": [ @@ -279114,6 +279353,13 @@ {} ] ], + "worker-data-set-timeout.sub.html": [ + "ac4b608b08c4ed3867f587847cc699a91cedc5c4", + [ + null, + {} + ] + ], "worker-eval-blocked.sub.html": [ "9a264f2a240bfb89b29aeee7ec39fb1e035b0f52", [ @@ -286193,6 +286439,13 @@ {} ] ], + "flex-minimum-height-flex-items-025.html": [ + "2c1f6fa64c6711b5c4c6dd32d5e2055ff6e47689", + [ + null, + {} + ] + ], "flex-minimum-size-001.html": [ "c2eea80ca5b79818f6b7a9a36ab1ff64826b5218", [ @@ -287000,6 +287253,13 @@ {} ] ], + "justify-content-006.html": [ + "354763c90ce19d4d41a3b1b2b33db14759b2ab45", + [ + null, + {} + ] + ], "justify-content_space-between-002.html": [ "fde1a7312408d02fe3843452585de2ee660ae6f7", [ @@ -312217,7 +312477,7 @@ ] ], "aria-element-reflection.tentative.html": [ - "9073e52075a502943cf9dfb7586174e0533b63fa", + "fc20df6696e975d368b5e18cfad776c4284615fe", [ null, {} @@ -363008,7 +363268,7 @@ ] ], "canvas-aspect-ratio.html": [ - "f523fd7219b204f1a7ac91805c206563d354e3ba", + "91fdc6c86c55595c2484a88a4e026827c3608581", [ null, {} @@ -369810,14 +370070,14 @@ }, "the-popup-element": { "popup-element-basic.tentative.html": [ - "41fa7d4e7f1adefc790e2e39b3b1e1a450499516", + "3be3fae9ac9a5bbccc6784a1efb87240574207b8", [ null, {} ] ], - "popup-open.tentative.html": [ - "d622b7d10538625d9db3858a933d7740e323986f", + "popup-shadow-dom.tentative.html": [ + "f17bdabac4b2bdd1f2e615e3bf90cf6ee1018059", [ null, {} @@ -385800,7 +386060,7 @@ ] ], "showPicker-errors.https.window.js": [ - "ecc64dfe66d6dd9611b33c75cd2504d11814eaf7", + "2310c323d9f37d317a65e2bb6cdcc001ebfa3568", [ "native-file-system/showPicker-errors.https.window.html", { @@ -392887,7 +393147,7 @@ }, "raw-sockets": { "open-consume-activation.https.html": [ - "f6dda4488bafef83fddb4af0e9958ae8a5892153", + "6cbf017712b197886fb2d8d620927ee03506f6d4", [ null, { @@ -423915,7 +424175,7 @@ ] ], "request-video-frame-callback-webrtc.https.html": [ - "ce5b5ad6be699a917b3dce5559cdbfa8e334127d", + "dcf97e4ca9105d7e29e579bdb632db554c525933", [ null, {} @@ -431024,7 +431284,7 @@ }, "webcodecs": { "audio-decoder.any.js": [ - "a6367231b234a6e078bd96dc2e689ffb429593c6", + "17f5520d2c7bfd40d2949d96d452f3142c70f438", [ "webcodecs/audio-decoder.any.html", { @@ -431075,7 +431335,7 @@ ] ], "video-decoder.any.js": [ - "44e7375a78c013d0a9d0f60a33a04651473c48fb", + "48568c170fc6195d924ac2b14be85dff00d3709d", [ "webcodecs/video-decoder.any.html", {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-tables/absolute-crash.html b/third_party/blink/web_tests/external/wpt/css/css-tables/absolute-crash.html new file mode 100644 index 0000000..d4558256 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-tables/absolute-crash.html
@@ -0,0 +1,10 @@ +<!DOCTYPE html> +<link rel="help" href="https://crbug.com/958381"> +<table style="position: relative; max-height: 10px;"> + <caption>caption</caption> + <td><div id=target style="position: absolute;"></div></td> +</table> +<script> + document.body.offsetTop; + document.getElementById('target').style.top = '10px'; +</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-tables/fixed-layout-2-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-tables/fixed-layout-2-expected.txt new file mode 100644 index 0000000..e74c8c8 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-tables/fixed-layout-2-expected.txt
@@ -0,0 +1,8 @@ +This is a testharness.js-based test. +PASS Table-layout:fixed is not applied when width is auto +PASS Table-layout:fixed reports fixed when width is auto +PASS Table-layout:fixed is not applied when width is max-content +PASS Table-layout:fixed reports fixed when width is max-content +FAIL Table-layout:fixed is applied when width is min-content assert_equals: expected 100 but got 50 +Harness: the test ran to completion. +
diff --git a/third_party/blink/web_tests/external/wpt/css/css-tables/fixed-layout-2.html b/third_party/blink/web_tests/external/wpt/css/css-tables/fixed-layout-2.html index 13a46642..dcbabb1f 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-tables/fixed-layout-2.html +++ b/third_party/blink/web_tests/external/wpt/css/css-tables/fixed-layout-2.html
@@ -30,8 +30,8 @@ <hr/> <p>This should be a 100px-wide blue square:</p> - <p>Table-layout:fixed does not apply to width:min-content/fit-content tables</p> - <x-table style="table-layout: auto; width: fit-content; border-spacing: 0px"> + <p>Table-layout:fixed does apply to width:min-content/fit-content tables</p> + <x-table style="table-layout: fixed; width: fit-content; border-spacing: 0px"> <x-tr> <x-td style="padding: 0; background: blue; height: 50px;"><div style="width: 100px"></div></x-td> <x-td style="padding: 0"></x-td> @@ -39,8 +39,8 @@ </x-table> <x-table style="table-layout: fixed; width: min-content; border-spacing: 0px"> <x-tr> - <x-td style="padding: 0; background: blue; height: 50px;"><div style="width: 100px"></div></x-td> - <x-td style="padding: 0"></x-td> + <x-td style="padding: 0; background: blue; height: 50px;width:100px;"><div style="width: 100px"></div></x-td> + <x-td style="padding: 0;height:50px"><div style="width: 100px"></div></x-td> </x-tr> </x-table> @@ -75,10 +75,10 @@ 'fixed' ], [ - "Table-layout:fixed is not applied when width is min-content", + "Table-layout:fixed is applied when width is min-content", document.querySelector("x-table:nth-of-type(3) > x-tr:first-child > x-td:first-child").offsetWidth, document.querySelector("x-table:nth-of-type(4) > x-tr:first-child > x-td:first-child").offsetWidth - ], + ] ]) </script>
diff --git a/third_party/blink/web_tests/external/wpt/resources/testharness.js b/third_party/blink/web_tests/external/wpt/resources/testharness.js index 3623aa4..adfb692c 100644 --- a/third_party/blink/web_tests/external/wpt/resources/testharness.js +++ b/third_party/blink/web_tests/external/wpt/resources/testharness.js
@@ -3570,9 +3570,11 @@ escape_html(test.message ? tests[i].message : " ") + (tests[i].stack ? "<pre>" + escape_html(tests[i].stack) + - "</pre>": "") + - "<details><summary>Asserts run</summary>" + get_asserts_output(test) + "</details>" - "</td></tr>"; + "</pre>": ""); + if (!(test instanceof RemoteTest)) { + html += "<details><summary>Asserts run</summary>" + get_asserts_output(test) + "</details>" + } + html += "</td></tr>"; } html += "</tbody></table>"; try {
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html index 36bf604..bf50195 100644 --- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html +++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html
@@ -95,8 +95,14 @@ }; should(() => { node = new OscillatorNode(context, options); - }, 'new OscillatorNode(, ' + JSON.stringify(options) + ')').notThrow(); + }, 'new OscillatorNode(c, ' + JSON.stringify(options) + ')').notThrow(); + should( + () => { + node = new OscillatorNode(context, {periodicWave: null}); + }, + 'new OscillatorNode(c, {periodicWave: null}') + .throw(DOMException, 'TypeError'); task.done(); });
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/utils.js b/third_party/blink/web_tests/external/wpt/webcodecs/utils.js index 940c2c2..9b0a022 100644 --- a/third_party/blink/web_tests/external/wpt/webcodecs/utils.js +++ b/third_party/blink/web_tests/external/wpt/webcodecs/utils.js
@@ -123,4 +123,20 @@ encodeOrDecodeShouldThrow(codec, codecInput); return promise_rejects_dom(test, 'InvalidStateError', codec.flush(), 'flush'); -} \ No newline at end of file +} + +// Verifies a PlaneInit structure matches the actual constructed plane. +function verifyPlane(expected, actual) { + assert_less_than_equal(expected.stride, actual.stride, 'plane strides'); + assert_equals(expected.rows, actual.rows, 'plane rows'); + assert_less_than_equal( + expected.stride * expected.rows, actual.length, 'plane size'); + + var testBuffer = new Uint8Array(actual.length); + actual.readInto(testBuffer); + for (var h = 0; h < actual.rows; ++h) { + assert_array_equals( + expected.src.slice(h * expected.stride, expected.stride), + testBuffer.slice(h * actual.stride, expected.stride), 'plane data'); + } +}
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/video-frame.any.js b/third_party/blink/web_tests/external/wpt/webcodecs/video-frame.any.js index 14cce43..b02badc 100644 --- a/third_party/blink/web_tests/external/wpt/webcodecs/video-frame.any.js +++ b/third_party/blink/web_tests/external/wpt/webcodecs/video-frame.any.js
@@ -97,3 +97,324 @@ let frame = new VideoFrame(image, {timestamp: 10}); }) }, 'Test constructing VideoFrames from closed ImageBitmap throws.'); + +test(t => { + let vfInit = {timestamp: 1234, codedWidth: 4, codedHeight: 2}; + assert_throws_js(TypeError, () => { + let frame = new VideoFrame('ABCD', [], vfInit); + }, 'invalid pixel format'); + + assert_throws_dom('ConstraintError', () => { + let frame = new VideoFrame('ARGB', [], {timestamp: 1234}); + }, 'missing coded size'); + + function constructFrame(init) { + let yPlaneData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); // 4x2 + let uPlaneData = new Uint8Array([1, 2]); // 2x1 + let yPlane = {src: yPlaneData, stride: 4, rows: 2}; + let uPlane = vPlane = {src: uPlaneData, stride: 2, rows: 1}; + let frame = new VideoFrame('I420', [yPlane, uPlane, vPlane], init); + } + + assert_throws_dom( + 'ConstraintError', () => {constructFrame({ + timestamp: 1234, + codedWidth: 1 << 32 - 1, + codedHeight: 1 << 32 - 1 + })}, + 'invalid coded size'); + assert_throws_dom( + 'ConstraintError', + () => {constructFrame({timestamp: 1234, codedWidth: 4, codedHeight: 0})}, + 'invalid coded height'); + assert_throws_dom( + 'ConstraintError', + () => {constructFrame({timestamp: 1234, codedWidth: 0, codedHeight: 4})}, + 'invalid coded width'); + assert_throws_dom( + 'ConstraintError', () => {constructFrame({ + timestamp: 1234, + codedWidth: 4, + codedHeight: 2, + cropLeft: 100, + cropRight: 100 + })}, + 'invalid crop left/right'); + assert_throws_dom( + 'ConstraintError', + () => {constructFrame( + {timestamp: 1234, codedWidth: 4, codedHeight: 2, cropWidth: 0})}, + 'invalid crop width'); + assert_throws_dom( + 'ConstraintError', + () => {constructFrame( + {timestamp: 1234, codedWidth: 4, codedHeight: 2, cropHeight: 0})}, + 'invalid crop height'); + assert_throws_dom( + 'ConstraintError', () => {constructFrame({ + timestamp: 1234, + codedWidth: 4, + codedHeight: 2, + cropHeight: -1, + cropWidth: -100 + })}, + 'invalid negative crop'); + assert_throws_dom( + 'ConstraintError', () => {constructFrame({ + timestamp: 1234, + codedWidth: 4, + codedHeight: 2, + displayWidth: 1 << 32 - 1 + })}, + 'invalid display width'); + assert_throws_dom( + 'ConstraintError', () => {constructFrame({ + timestamp: 1234, + codedWidth: 4, + codedHeight: 2, + displayWidth: 1 << 32 - 1, + displayHeight: 1 << 32 + })}, + 'invalid display height'); +}, 'Test invalid planar constructed VideoFrames'); + +test(t => { + let fmt = 'I420'; + let vfInit = {timestamp: 1234, codedWidth: 4, codedHeight: 2}; + let yPlaneData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); // 4x2 + let uPlaneData = new Uint8Array([1, 2]); // 2x1 + let yPlane = {src: yPlaneData, stride: 4, rows: 2}; + let uPlane = vPlane = {src: uPlaneData, stride: 2, rows: 1}; + let frame = new VideoFrame(fmt, [yPlane, uPlane, vPlane], vfInit); + assert_equals(frame.planes.length, 3, 'plane count'); + assert_equals(frame.format, fmt, 'plane format'); + verifyPlane(yPlane, frame.planes[0]); + verifyPlane(uPlane, frame.planes[1]); + verifyPlane(vPlane, frame.planes[2]); + frame.close(); + + assert_throws_dom('ConstraintError', () => { + let frame = new VideoFrame(fmt, [yPlane, uPlane], vfInit); + }, 'too few planes'); + assert_throws_dom('ConstraintError', () => { + let badYPlane = {...yPlane}; + badYPlane.stride = 1; + let frame = new VideoFrame(fmt, [badYPlane, uPlane, vPlane], vfInit); + }, 'y stride too small'); + assert_throws_dom('ConstraintError', () => { + let badUPlane = {...uPlane}; + badUPlane.stride = 1; + let frame = new VideoFrame(fmt, [yPlane, badUPlane, vPlane], vfInit); + }, 'u stride too small'); + assert_throws_dom('ConstraintError', () => { + let badVPlane = {...vPlane}; + badVPlane.stride = 1; + let frame = new VideoFrame(fmt, [yPlane, uPlane, badVPlane], vfInit); + }, 'v stride too small'); + assert_throws_dom('ConstraintError', () => { + let badYPlane = {...yPlane}; + badYPlane.rows = 1; + let frame = new VideoFrame(fmt, [badYPlane, uPlane, vPlane], vfInit); + }, 'y height too small'); + assert_throws_dom('ConstraintError', () => { + let badUPlane = {...uPlane}; + badUPlane.rows = 0; + let frame = new VideoFrame(fmt, [yPlane, badUPlane, vPlane], vfInit); + }, 'u height too small'); + assert_throws_dom('ConstraintError', () => { + let badVPlane = {...vPlane}; + badVPlane.rows = 0; + let frame = new VideoFrame(fmt, [yPlane, uPlane, badVPlane], vfInit); + }, 'v height too small'); + assert_throws_dom('ConstraintError', () => { + let badYPlane = {...yPlane}; + badYPlane.rows = 100; + let frame = new VideoFrame(fmt, [badYPlane, uPlane, vPlane], vfInit); + }, 'y height too large'); + assert_throws_dom('ConstraintError', () => { + let badUPlane = {...uPlane}; + badUPlane.rows = 100; + let frame = new VideoFrame(fmt, [yPlane, badUPlane, vPlane], vfInit); + }, 'u height too large'); + assert_throws_dom('ConstraintError', () => { + let badVPlane = {...vPlane}; + badVPlane.rows = 100; + let frame = new VideoFrame(fmt, [yPlane, uPlane, badVPlane], vfInit); + }, 'v height too large'); + assert_throws_dom('ConstraintError', () => { + let badYPlane = {...yPlane}; + badYPlane.src = yPlaneData.slice(1, 4); + let frame = new VideoFrame(fmt, [badYPlane, uPlane, vPlane], vfInit); + }, 'y plane size too small'); + assert_throws_dom('ConstraintError', () => { + let badUPlane = {...uPlane}; + badUPlane.src = uPlaneData.slice(1, 1); + let frame = new VideoFrame(fmt, [yPlane, badUPlane, vPlane], vfInit); + }, 'u plane size too small'); + assert_throws_dom('ConstraintError', () => { + let badVPlane = {...vPlane}; + badVPlane.src = uPlaneData.slice(1, 1); + let frame = new VideoFrame(fmt, [yPlane, uPlane, badVPlane], vfInit); + }, 'v plane size too small'); +}, 'Test planar constructed I420 VideoFrame'); + +test(t => { + let fmt = 'I420'; + let vfInit = {timestamp: 1234, codedWidth: 4, codedHeight: 2}; + let yPlaneData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); // 4x2 + let uPlaneData = new Uint8Array([1, 2]); // 2x1 + let yPlane = {src: yPlaneData, stride: 4, rows: 2}; + let uPlane = vPlane = {src: uPlaneData, stride: 2, rows: 1}; + let aPlaneData = yPlaneData.reverse(); + let aPlane = {src: aPlaneData, stride: 4, rows: 2}; + let frame = new VideoFrame(fmt, [yPlane, uPlane, vPlane, aPlane], vfInit); + assert_equals(frame.planes.length, 4, 'plane count'); + assert_equals(frame.format, fmt, 'plane format'); + verifyPlane(yPlane, frame.planes[0]); + verifyPlane(uPlane, frame.planes[1]); + verifyPlane(vPlane, frame.planes[2]); + verifyPlane(aPlane, frame.planes[3]); + frame.close(); + + // Most constraints are tested as part of I420 above. + + assert_throws_dom('ConstraintError', () => { + let badAPlane = {...aPlane}; + badAPlane.stride = 1; + let frame = + new VideoFrame(fmt, [yPlane, uPlane, vPlane, badAPlane], vfInit); + }, 'a stride too small'); + assert_throws_dom('ConstraintError', () => { + let badAPlane = {...aPlane}; + badAPlane.rows = 1; + let frame = + new VideoFrame(fmt, [yPlane, uPlane, vPlane, badAPlane], vfInit); + }, 'a height too small'); + assert_throws_dom('ConstraintError', () => { + let badAPlane = {...aPlane}; + badAPlane.rows = 100; + let frame = + new VideoFrame(fmt, [yPlane, uPlane, vPlane, badAPlane], vfInit); + }, 'a height too large'); + assert_throws_dom('ConstraintError', () => { + let badAPlane = {...yPlane}; + badAPlane.src = aPlaneData.slice(1, 4); + let frame = + new VideoFrame(fmt, [yPlane, uPlane, vPlane, badAPlane], vfInit); + }, 'a plane size too small'); +}, 'Test planar constructed I420+Alpha VideoFrame'); + +test(t => { + let fmt = 'NV12'; + let vfInit = {timestamp: 1234, codedWidth: 4, codedHeight: 2}; + let yPlaneData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); // 4x2 + let yPlane = {src: yPlaneData, stride: 4, rows: 2}; + let uvPlaneData = new Uint8Array([1, 2, 3, 4]); + let uvPlane = {src: uvPlaneData, stride: 4, rows: 1}; + frame = new VideoFrame(fmt, [yPlane, uvPlane], vfInit); + assert_equals(frame.planes.length, 2, 'plane count'); + assert_equals(frame.format, fmt, 'plane format'); + verifyPlane(yPlane, frame.planes[0]); + verifyPlane(uvPlane, frame.planes[1]); + frame.close(); + + assert_throws_dom('ConstraintError', () => { + let frame = new VideoFrame(fmt, [yPlane], vfInit); + }, 'too few planes'); + assert_throws_dom('ConstraintError', () => { + let badYPlane = {...yPlane}; + badYPlane.stride = 1; + let frame = new VideoFrame(fmt, [badYPlane, uvPlane], vfInit); + }, 'y stride too small'); + assert_throws_dom('ConstraintError', () => { + let badUVPlane = {...uvPlane}; + badUVPlane.stride = 2; + let frame = new VideoFrame(fmt, [yPlane, badUVPlane], vfInit); + }, 'uv stride too small'); + assert_throws_dom('ConstraintError', () => { + let badYPlane = {...yPlane}; + badYPlane.rows = 1; + let frame = new VideoFrame(fmt, [badYPlane, uvPlane], vfInit); + }, 'y height too small'); + assert_throws_dom('ConstraintError', () => { + let badUVPlane = {...uvPlane}; + badUVPlane.rows = 0; + let frame = new VideoFrame(fmt, [yPlane, badUVPlane], vfInit); + }, 'uv height too small'); + assert_throws_dom('ConstraintError', () => { + let badYPlane = {...yPlane}; + badYPlane.rows = 100; + let frame = new VideoFrame(fmt, [badYPlane, uvPlane], vfInit); + }, 'y height too large'); + assert_throws_dom('ConstraintError', () => { + let badUVPlane = {...uvPlane}; + badUVPlane.rows = 100; + let frame = new VideoFrame(fmt, [yPlane, badUVPlane], vfInit); + }, 'u height too large'); + assert_throws_dom('ConstraintError', () => { + let badYPlane = {...yPlane}; + badYPlane.src = yPlaneData.slice(1, 4); + let frame = new VideoFrame(fmt, [badYPlane, uvPlane], vfInit); + }, 'y plane size too small'); + assert_throws_dom('ConstraintError', () => { + let badUVPlane = {...uvPlane}; + badUVPlane.src = uvPlaneData.slice(1, 1); + let frame = new VideoFrame(fmt, [yPlane, badUVPlane], vfInit); + }, 'u plane size too small'); +}, 'Test planar constructed NV12 VideoFrame'); + +test(t => { + let vfInit = {timestamp: 1234, codedWidth: 4, codedHeight: 2}; + let argbPlaneData = + new Uint8Array(new Uint32Array([1, 2, 3, 4, 5, 6, 7, 8]).buffer); + let argbPlane = {src: argbPlaneData, stride: 4 * 4, rows: 2}; + frame = new VideoFrame('ABGR', [argbPlane], vfInit); + assert_equals(frame.planes.length, 1, 'plane count'); + assert_equals(frame.format, 'ABGR', 'plane format'); + verifyPlane(argbPlane, frame.planes[0]); + frame.close(); + + frame = new VideoFrame('ARGB', [argbPlane], vfInit); + assert_equals(frame.planes.length, 1, 'plane count'); + assert_equals(frame.format, 'ARGB', 'plane format'); + verifyPlane(argbPlane, frame.planes[0]); + frame.close(); + + frame = new VideoFrame('XBGR', [argbPlane], vfInit); + assert_equals(frame.planes.length, 1, 'plane count'); + assert_equals(frame.format, 'XBGR', 'plane format'); + verifyPlane(argbPlane, frame.planes[0]); + frame.close(); + + frame = new VideoFrame('XRGB', [argbPlane], vfInit); + assert_equals(frame.planes.length, 1, 'plane count'); + assert_equals(frame.format, 'XRGB', 'plane format'); + verifyPlane(argbPlane, frame.planes[0]); + frame.close(); + + ['ABGR', 'ARGB', 'XBGR', 'XRGB'].forEach(fmt => { + assert_throws_dom('ConstraintError', () => { + let frame = new VideoFrame(fmt, [], vfInit); + }, fmt + ': too few planes'); + assert_throws_dom('ConstraintError', () => { + let badARGBPlane = {...argbPlane}; + badARGBPlane.stride = 1; + let frame = new VideoFrame(fmt, [badARGBPlane], vfInit); + }, fmt + ': stride too small'); + assert_throws_dom('ConstraintError', () => { + let badARGBPlane = {...argbPlane}; + badARGBPlane.rows = 1; + let frame = new VideoFrame(fmt, [badARGBPlane], vfInit); + }, fmt + ': height too small'); + assert_throws_dom('ConstraintError', () => { + let badARGBPlane = {...argbPlane}; + badARGBPlane.rows = 100; + let frame = new VideoFrame(fmt, [badARGBPlane], vfInit); + }, fmt + ': height too large'); + assert_throws_dom('ConstraintError', () => { + let badARGBPlane = {...argbPlane}; + badARGBPlane.src = argbPlaneData.slice(1, 4); + let frame = new VideoFrame(fmt, [badARGBPlane], vfInit); + }, fmt + ': plane size too small'); + }); +}, 'Test planar constructed RGB VideoFrames');
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/css/css-tables/fixed-layout-2-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/css/css-tables/fixed-layout-2-expected.txt new file mode 100644 index 0000000..24414c3 --- /dev/null +++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/css/css-tables/fixed-layout-2-expected.txt
@@ -0,0 +1,8 @@ +This is a testharness.js-based test. +PASS Table-layout:fixed is not applied when width is auto +PASS Table-layout:fixed reports fixed when width is auto +PASS Table-layout:fixed is not applied when width is max-content +PASS Table-layout:fixed reports fixed when width is max-content +FAIL Table-layout:fixed is applied when width is min-content assert_equals: expected 100 but got 0 +Harness: the test ran to completion. +
diff --git a/third_party/blink/web_tests/http/tests/eye-dropper/color-picker-show-eye-dropper.html b/third_party/blink/web_tests/http/tests/eye-dropper/color-picker-show-eye-dropper.html new file mode 100644 index 0000000..54f9831 --- /dev/null +++ b/third_party/blink/web_tests/http/tests/eye-dropper/color-picker-show-eye-dropper.html
@@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> +<head> +<meta name="color-scheme" content="light dark"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/forms-test-resources/picker-common.js"></script> +</head> +<body> +<input type='color' id='color' value='#7EFFC9'> + +<p id='description' style='opacity: 0'></p> +<div id='console' style='opacity: 0'></div> + +<script type="module"> +import {waitUntilEyeDropperShown} from './resources/mock-eyedropperchooser.js'; + +promise_test(async () => { + await new Promise((resolve, reject) => { + openPicker(document.getElementById('color'), resolve, reject); + }); + + popupWindow.focus(); + const popupDocument = popupWindow.document; + const eyeDropper = popupDocument.querySelector('eye-dropper'); + const eyeDropperRect = eyeDropper.getBoundingClientRect(); + eventSender.mouseMoveTo(eyeDropperRect.left + (eyeDropperRect.width / 2), eyeDropperRect.top + (eyeDropperRect.height / 2)); + eventSender.mouseDown(); + eventSender.mouseUp(); + await waitUntilEyeDropperShown(); +}, 'eye dropper is shown when clicked in color chooser'); +</script> +</body> +</html>
diff --git a/third_party/blink/web_tests/http/tests/eye-dropper/resources/mock-eyedropperchooser.js b/third_party/blink/web_tests/http/tests/eye-dropper/resources/mock-eyedropperchooser.js new file mode 100644 index 0000000..c8d6636 --- /dev/null +++ b/third_party/blink/web_tests/http/tests/eye-dropper/resources/mock-eyedropperchooser.js
@@ -0,0 +1,43 @@ +import {EyeDropperChooser, EyeDropperChooserReceiver} from '/gen/third_party/blink/public/mojom/choosers/color_chooser.mojom.m.js'; + +class MockEyeDropperChooser { + constructor() { + this.receiver_ = new EyeDropperChooserReceiver(this); + this.interceptor_ = + new MojoInterfaceInterceptor(EyeDropperChooser.$interfaceName); + this.interceptor_.oninterfacerequest = + e => this.receiver_.$.bindHandle(e.handle); + this.interceptor_.start(); + + this.receiver_.onConnectionError.addListener(() => { + this.count_--; + }); + this.count_ = 0; + this.shownResolvers_ = []; + } + + choose() { + this.count_++; + if (this.count_ == 1) { + this.shownResolvers_.forEach(r => r()); + } + return new Promise((resolve, reject) => { + // TODO(crbug.com/992297): handle value chosen. + }); + } + + async waitUntilShown() { + if (this.count_ > 0) { + return; + } + return new Promise(resolve => { + this.shownResolvers_.push(resolve); + }); + } +} + +let mockEyeDropperChooser = new MockEyeDropperChooser(); + +export async function waitUntilEyeDropperShown() { + return mockEyeDropperChooser.waitUntilShown(); +}
diff --git a/third_party/blink/web_tests/virtual/eye-dropper/README.txt b/third_party/blink/web_tests/virtual/eye-dropper/README.txt deleted file mode 100644 index 4119e71..0000000 --- a/third_party/blink/web_tests/virtual/eye-dropper/README.txt +++ /dev/null
@@ -1 +0,0 @@ -# This suite runs tests with --enable-features=EyeDropper
diff --git a/third_party/blink/web_tests/virtual/eye-dropper/color-picker-show-eye-dropper.html b/third_party/blink/web_tests/virtual/eye-dropper/color-picker-show-eye-dropper.html deleted file mode 100644 index cdb02901..0000000 --- a/third_party/blink/web_tests/virtual/eye-dropper/color-picker-show-eye-dropper.html +++ /dev/null
@@ -1,34 +0,0 @@ -<!DOCTYPE html> -<html> -<head> -<meta name="color-scheme" content="light dark"> -<script> -testRunner.waitUntilDone(); -</script> -<script src='../../fast/forms/resources/picker-common.js'></script> -<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script> -<script src="file:///gen/third_party/blink/public/mojom/choosers/color_chooser.mojom.js"></script> -<script src='mock-eyedropperchooser.js'></script> -</head> -<body> -<input type='color' id='color' value='#7EFFC9'> - -<p id='description' style='opacity: 0'></p> -<div id='console' style='opacity: 0'></div> - -<script> -openPicker(document.getElementById('color'), openPickerSuccessfulCallback, () => testRunner.notifyDone()); - -function openPickerSuccessfulCallback() { - popupWindow.focus(); - const popupDocument = popupWindow.document; - waitUntilEyeDropperShown(() => testRunner.notifyDone()); - const eyeDropper = popupDocument.querySelector('eye-dropper'); - const eyeDropperRect = eyeDropper.getBoundingClientRect(); - eventSender.mouseMoveTo(eyeDropperRect.left + (eyeDropperRect.width / 2), eyeDropperRect.top + (eyeDropperRect.height / 2)); - eventSender.mouseDown(); - eventSender.mouseUp(); -} -</script> -</body> -</html> \ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/eye-dropper/mock-eyedropperchooser.js b/third_party/blink/web_tests/virtual/eye-dropper/mock-eyedropperchooser.js deleted file mode 100644 index 2af9fe6..0000000 --- a/third_party/blink/web_tests/virtual/eye-dropper/mock-eyedropperchooser.js +++ /dev/null
@@ -1,44 +0,0 @@ -'use strict'; - -class MockEyeDropperChooser { - constructor() { - this.bindingSet_ = new mojo.BindingSet(blink.mojom.EyeDropperChooser); - this.interceptor_ = - new MojoInterfaceInterceptor(blink.mojom.EyeDropperChooser.name); - this.interceptor_.oninterfacerequest = - e => this.bindingSet_.addBinding(this, e.handle); - this.interceptor_.start(); - - this.bindingSet_.setConnectionErrorHandler(() => { - this.count_--; - }); - this.count_ = 0; - } - - choose() { - this.count_++; - return new Promise((resolve, reject) => { - // TODO(crbug.com/992297): handle value chosen. - }); - } - - isChooserShown() { - return this.count_ > 0; - } -} - -let mockEyeDropperChooser = new MockEyeDropperChooser(); - -function waitUntilEyeDropperShown(then) { - if (!mockEyeDropperChooser.isChooserShown()) - return setTimeout(() => { waitUntilEyeDropperShown(then); }, 0); - if (then) - then(); -} - -function waitUntilEyeDropperClosed(then) { - if (mockEyeDropperChooser.isChooserShown()) - return setTimeout(() => { waitUntilEyeDropperClosed(then); }, 0); - if (then) - then(); -}
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt index 05eea9c..c64875a 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -10512,18 +10512,22 @@ attribute @@toStringTag getter boundsGeometry method constructor +interface XRCPUDepthInformation : XRDepthInformation + attribute @@toStringTag + getter data + method constructor + method getDepthInMeters interface XRDOMOverlayState attribute @@toStringTag getter type method constructor interface XRDepthInformation attribute @@toStringTag - getter data getter height getter normTextureFromNormView + getter rawValueToMeters getter width method constructor - method getDepth interface XRFrame attribute @@toStringTag getter detectedPlanes @@ -10676,6 +10680,8 @@ method constructor interface XRSession : EventTarget attribute @@toStringTag + getter depthDataFormat + getter depthUsage getter domOverlayState getter environmentBlendMode getter inputSources @@ -10760,7 +10766,12 @@ attribute @@toStringTag method constructor method getCameraImage + method getDepthInformation method getReflectionCubeMap +interface XRWebGLDepthInformation : XRDepthInformation + attribute @@toStringTag + getter texture + method constructor interface XRWebGLLayer : XRLayer static method getNativeFramebufferScaleFactor attribute @@toStringTag
diff --git a/third_party/cldr/LICENSE b/third_party/cldr/LICENSE new file mode 100644 index 0000000..a9f3977 --- /dev/null +++ b/third_party/cldr/LICENSE
@@ -0,0 +1,46 @@ +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +See Terms of Use for definitions of Unicode Inc.'s +Data Files and Software. + +NOTICE TO USER: Carefully read the following legal agreement. +BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S +DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), +YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE +THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2021 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in https://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder.
diff --git a/third_party/cldr/OWNERS b/third_party/cldr/OWNERS new file mode 100644 index 0000000..ea320a3 --- /dev/null +++ b/third_party/cldr/OWNERS
@@ -0,0 +1,3 @@ +jopalmer@chromium.org +keithlee@chromium.org +file://chrome/browser/chromeos/input_method/OWNERS
diff --git a/third_party/cldr/README.chromium b/third_party/cldr/README.chromium new file mode 100644 index 0000000..e3d445d1 --- /dev/null +++ b/third_party/cldr/README.chromium
@@ -0,0 +1,16 @@ +Name: Unicode Common Locale Data Repository +Short Name: cldr +URL: http://cldr.unicode.org/index/downloads +Version: 38.1 +License: MIT-like +License File: LICENSE +Security Critical: yes + +Description: +The Unicode CLDR provides data files to support internationalisation. +Currently, this is used to provide emoji keywords and names to facilitate +search in the Chrome OS emoji picker. + +Local Modifications: +Only the files relevant to English emoji keywords are currently checked out. +See ./update.sh for details and how to update.
diff --git a/third_party/cldr/src/common/annotations/en.xml b/third_party/cldr/src/common/annotations/en.xml new file mode 100644 index 0000000..843a3af6 --- /dev/null +++ b/third_party/cldr/src/common/annotations/en.xml
@@ -0,0 +1,3771 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE ldml SYSTEM "../../common/dtd/ldml.dtd"> +<!-- Copyright © 1991-2020 Unicode, Inc. +For terms of use, see http://www.unicode.org/copyright.html +Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/) + +the U.S. and other countries. CLDR data files are interpreted according to +the LDML specification (http://unicode.org/reports/tr35/) Warnings: All cp +values have U+FE0F characters removed. See /annotationsDerived/ for derived +annotations. +--> +<ldml> + <identity> + <version number="$Revision$"/> + <language type="en"/> + </identity> + <annotations> + <annotation cp="{">brace | bracket | curly brace | curly bracket | gullwing | open curly bracket</annotation> + <annotation cp="{" type="tts">open curly bracket</annotation> + <annotation cp="🏻">light skin tone | skin tone | type 1–2</annotation> + <annotation cp="🏻" type="tts">light skin tone</annotation> + <annotation cp="🏼">medium-light skin tone | skin tone | type 3</annotation> + <annotation cp="🏼" type="tts">medium-light skin tone</annotation> + <annotation cp="🏽">medium skin tone | skin tone | type 4</annotation> + <annotation cp="🏽" type="tts">medium skin tone</annotation> + <annotation cp="🏾">medium-dark skin tone | skin tone | type 5</annotation> + <annotation cp="🏾" type="tts">medium-dark skin tone</annotation> + <annotation cp="🏿">dark skin tone | skin tone | type 6</annotation> + <annotation cp="🏿" type="tts">dark skin tone</annotation> + <annotation cp="‾">overline | overstrike | vinculum</annotation> + <annotation cp="‾" type="tts">overline</annotation> + <annotation cp="_">dash | line | low dash | low line | underdash | underline</annotation> + <annotation cp="_" type="tts">low line</annotation> + <annotation cp="-">dash | hyphen | hyphen-minus | minus</annotation> + <annotation cp="-" type="tts">hyphen-minus</annotation> + <annotation cp="‐">dash | hyphen</annotation> + <annotation cp="‐" type="tts">hyphen</annotation> + <annotation cp="–">dash | en</annotation> + <annotation cp="–" type="tts">en dash</annotation> + <annotation cp="—">dash | em</annotation> + <annotation cp="—" type="tts">em dash</annotation> + <annotation cp="―">bar | dash | horizontal bar | line</annotation> + <annotation cp="―" type="tts">horizontal bar</annotation> + <annotation cp="・">dots | interpunct | katakana | katakana middle dot | middot</annotation> + <annotation cp="・" type="tts">katakana middle dot</annotation> + <!-- causes tool breakage + <annotation cp="">whitespace</annotation> + <annotation cp="" type="tts">space</annotation> + --> + <annotation cp=",">comma</annotation> + <annotation cp="," type="tts">comma</annotation> + <annotation cp="،">arabic | comma</annotation> + <annotation cp="،" type="tts">arabic comma</annotation> + <annotation cp="、">comma | ideographic</annotation> + <annotation cp="、" type="tts">ideographic comma</annotation> + <annotation cp=";">semi-colon | semicolon</annotation> + <annotation cp=";" type="tts">semicolon</annotation> + <annotation cp="؛">arabic | arabic semicolon | semi-colon</annotation> + <annotation cp="؛" type="tts">arabic semicolon</annotation> + <annotation cp=":">colon</annotation> + <annotation cp=":" type="tts">colon</annotation> + <annotation cp="!">bang | exclamation | exclamation mark | exclamation point</annotation> + <annotation cp="!" type="tts">exclamation mark</annotation> + <annotation cp="¡">bang | exclamation | exclamation mark | exclamation point | inverted | inverted exclamation mark</annotation> + <annotation cp="¡" type="tts">inverted exclamation mark</annotation> + <annotation cp="?">question | question mark</annotation> + <annotation cp="?" type="tts">question mark</annotation> + <annotation cp="¿">inverted | inverted question mark | question | question mark</annotation> + <annotation cp="¿" type="tts">inverted question mark</annotation> + <annotation cp="؟">arabic | arabic question mark | question | question mark</annotation> + <annotation cp="؟" type="tts">arabic question mark</annotation> + <annotation cp="‽">interabang | interrobang</annotation> + <annotation cp="‽" type="tts">interrobang</annotation> + <annotation cp=".">dot | full stop | period</annotation> + <annotation cp="." type="tts">period</annotation> + <annotation cp="…">dots | ellipsis | omission</annotation> + <annotation cp="…" type="tts">ellipsis</annotation> + <annotation cp="。">full stop | ideographic | period</annotation> + <annotation cp="。" type="tts">ideographic period</annotation> + <annotation cp="·">dot | interpunct | middle | middot</annotation> + <annotation cp="·" type="tts">middle dot</annotation> + <annotation cp="'">apostrophe | quote | single quote | typewriter apostrophe</annotation> + <annotation cp="'" type="tts">typewriter apostrophe</annotation> + <annotation cp="‘">apostrophe | left apostrophe | quote | single quote | smart quote</annotation> + <annotation cp="‘" type="tts">left apostrophe</annotation> + <annotation cp="’">apostrophe | quote | right apostrophe | single quote | smart quote</annotation> + <annotation cp="’" type="tts">right apostrophe</annotation> + <annotation cp="‚">apostrophe | low quote | low right apostrophe | quote</annotation> + <annotation cp="‚" type="tts">low right apostrophe</annotation> + <annotation cp="“">double quote | left quotation mark | quotation | quote | smart quotation</annotation> + <annotation cp="“" type="tts">left quotation mark</annotation> + <annotation cp="”">double quote | quotation | quote | right quotation mark | smart quotation</annotation> + <annotation cp="”" type="tts">right quotation mark</annotation> + <annotation cp="„">double quote | low quotation | low right quotation mark | quotation | quote | smart quotation</annotation> + <annotation cp="„" type="tts">low right quotation mark</annotation> + <annotation cp="«">angle | brackets | carets | chevron | left | left guillemet | quote</annotation> + <annotation cp="«" type="tts">left guillemet</annotation> + <annotation cp="»">angle | brackets | carets | chevron | quote | right | right guillemet</annotation> + <annotation cp="»" type="tts">right guillemet</annotation> + <annotation cp=")">bracket | close parenthesis | paren | parens | parenthesis | round bracket</annotation> + <annotation cp=")" type="tts">close parenthesis</annotation> + <annotation cp="[">bracket | crotchet | open square bracket | square bracket</annotation> + <annotation cp="[" type="tts">open square bracket</annotation> + <annotation cp="]">bracket | close square bracket | crotchet | square bracket</annotation> + <annotation cp="]" type="tts">close square bracket</annotation> + <annotation cp="}">brace | bracket | close curly bracket | curly brace | curly bracket | gullwing</annotation> + <annotation cp="}" type="tts">close curly bracket</annotation> + <annotation cp="〈">angle bracket | bracket | chevron | diamond brackets | open angle bracket | pointy bracket | tuple</annotation> + <annotation cp="〈" type="tts">open angle bracket</annotation> + <annotation cp="〉">angle bracket | bracket | chevron | close angle bracket | diamond brackets | pointy bracket | tuple</annotation> + <annotation cp="〉" type="tts">close angle bracket</annotation> + <annotation cp="《">bracket | double angle bracket | open double angle bracket</annotation> + <annotation cp="《" type="tts">open double angle bracket</annotation> + <annotation cp="》">bracket | close double angle bracket | double angle bracket</annotation> + <annotation cp="》" type="tts">close double angle bracket</annotation> + <annotation cp="「">bracket | corner bracket | open corner bracket</annotation> + <annotation cp="「" type="tts">open corner bracket</annotation> + <annotation cp="」">bracket | close corner bracket | corner bracket</annotation> + <annotation cp="」" type="tts">close corner bracket</annotation> + <annotation cp="『">bracket | hollow corner bracket | open hollow corner bracket</annotation> + <annotation cp="『" type="tts">open hollow corner bracket</annotation> + <annotation cp="』">bracket | close hollow corner bracket | hollow corner bracket</annotation> + <annotation cp="』" type="tts">close hollow corner bracket</annotation> + <annotation cp="【">bracket | lens bracket | lenticular bracket | open black lens bracket</annotation> + <annotation cp="【" type="tts">open black lens bracket</annotation> + <annotation cp="】">bracket | close black lens bracket | lens bracket | lenticular bracket</annotation> + <annotation cp="】" type="tts">close black lens bracket</annotation> + <annotation cp="〔">bracket | open tortoise shell bracket | shell bracket | tortoise shell bracket</annotation> + <annotation cp="〔" type="tts">open tortoise shell bracket</annotation> + <annotation cp="〕">bracket | close tortoise shell bracket | shell bracket | tortoise shell bracket</annotation> + <annotation cp="〕" type="tts">close tortoise shell bracket</annotation> + <annotation cp="〖">bracket | hollow lens bracket | hollow lenticular bracket | open hollow lens bracket</annotation> + <annotation cp="〖" type="tts">open hollow lens bracket</annotation> + <annotation cp="〗">bracket | close hollow lens bracket | hollow lens bracket | hollow lenticular bracket</annotation> + <annotation cp="〗" type="tts">close hollow lens bracket</annotation> + <annotation cp="§">paragraph | part | section | silcrow</annotation> + <annotation cp="§" type="tts">section</annotation> + <annotation cp="¶">alinea | paragraph | paragraph mark | paraph | pilcrow</annotation> + <annotation cp="¶" type="tts">paragraph mark</annotation> + <annotation cp="@">ampersat | arobase | arroba | at mark | at sign | at-sign | commercial at | strudel</annotation> + <annotation cp="@" type="tts">at-sign</annotation> + <annotation cp="*">asterisk | star | wildcard</annotation> + <annotation cp="*" type="tts">asterisk</annotation> + <annotation cp="/">oblique | slash | solidus | stroke | virgule | whack</annotation> + <annotation cp="/" type="tts">slash</annotation> + <annotation cp="\">backslash | reverse solidus | whack</annotation> + <annotation cp="\" type="tts">backslash</annotation> + <annotation cp="&">ampersand | and | et</annotation> + <annotation cp="&" type="tts">ampersand</annotation> + <annotation cp="#">hash | hash sign | hashtag | lb | number | pound</annotation> + <annotation cp="#" type="tts">hash sign</annotation> + <annotation cp="%">per cent | per-cent | percent</annotation> + <annotation cp="%" type="tts">percent</annotation> + <annotation cp="‰">per mil | per mille | permil | permille</annotation> + <annotation cp="‰" type="tts">per mille</annotation> + <annotation cp="†">dagger | dagger sign | obelisk | obelus</annotation> + <annotation cp="†" type="tts">dagger sign</annotation> + <annotation cp="‡">dagger | double | double dagger sign | obelisk | obelus</annotation> + <annotation cp="‡" type="tts">double dagger sign</annotation> + <annotation cp="•">bullet | dot</annotation> + <annotation cp="•" type="tts">bullet</annotation> + <annotation cp="‧">dash | hyphen | hyphenation point | point</annotation> + <annotation cp="‧" type="tts">hyphenation point</annotation> + <annotation cp="′">prime</annotation> + <annotation cp="′" type="tts">prime</annotation> + <annotation cp="″">double prime | prime</annotation> + <annotation cp="″" type="tts">double prime</annotation> + <annotation cp="‴">prime | triple prime</annotation> + <annotation cp="‴" type="tts">triple prime</annotation> + <annotation cp="‸">caret</annotation> + <annotation cp="‸" type="tts">caret</annotation> + <annotation cp="※">reference mark</annotation> + <annotation cp="※" type="tts">reference mark</annotation> + <annotation cp="⁂">asterism | dinkus | stars</annotation> + <annotation cp="⁂" type="tts">asterism</annotation> + <annotation cp="`">accent | grave | tone</annotation> + <annotation cp="`" type="tts">grave accent</annotation> + <annotation cp="´">accent | acute | tone</annotation> + <annotation cp="´" type="tts">acute accent</annotation> + <annotation cp="^">accent | caret | chevron | circumflex | control | hat | pointer | power | wedge | xor sign</annotation> + <annotation cp="^" type="tts">circumflex accent</annotation> + <annotation cp="¨">diaeresis | tréma | umlaut</annotation> + <annotation cp="¨" type="tts">diaeresis</annotation> + <annotation cp="°">degree | hour | proof</annotation> + <annotation cp="°" type="tts">degree</annotation> + <annotation cp="℗">copyright | recording | sound</annotation> + <annotation cp="℗" type="tts">sound recording copyright</annotation> + <annotation cp="←">arrow | left | left-pointing arrow</annotation> + <annotation cp="←" type="tts">left-pointing arrow</annotation> + <annotation cp="↚">leftwards arrow stroke</annotation> + <annotation cp="↚" type="tts">leftwards arrow stroke</annotation> + <annotation cp="→">arrow | right | right-pointing arrow</annotation> + <annotation cp="→" type="tts">right-pointing arrow</annotation> + <annotation cp="↛">rightwards arrow stroke</annotation> + <annotation cp="↛" type="tts">rightwards arrow stroke</annotation> + <annotation cp="↑">arrow | up | up-pointing arrow</annotation> + <annotation cp="↑" type="tts">up-pointing arrow</annotation> + <annotation cp="↓">arrow | down | down-pointing arrow</annotation> + <annotation cp="↓" type="tts">down-pointing arrow</annotation> + <annotation cp="↜">leftwards wave arrow</annotation> + <annotation cp="↜" type="tts">leftwards wave arrow</annotation> + <annotation cp="↝">rightwards wave arrow</annotation> + <annotation cp="↝" type="tts">rightwards wave arrow</annotation> + <annotation cp="↞">leftwards two headed arrow</annotation> + <annotation cp="↞" type="tts">leftwards two headed arrow</annotation> + <annotation cp="↟">upwards two headed arrow</annotation> + <annotation cp="↟" type="tts">upwards two headed arrow</annotation> + <annotation cp="↠">rightwards two headed arrow</annotation> + <annotation cp="↠" type="tts">rightwards two headed arrow</annotation> + <annotation cp="↡">downwards two headed arrow</annotation> + <annotation cp="↡" type="tts">downwards two headed arrow</annotation> + <annotation cp="↢">leftwards arrow tail</annotation> + <annotation cp="↢" type="tts">leftwards arrow tail</annotation> + <annotation cp="↣">rightwards arrow tail</annotation> + <annotation cp="↣" type="tts">rightwards arrow tail</annotation> + <annotation cp="↤">leftwards arrow from bar</annotation> + <annotation cp="↤" type="tts">leftwards arrow from bar</annotation> + <annotation cp="↥">upwards arrow from bar</annotation> + <annotation cp="↥" type="tts">upwards arrow from bar</annotation> + <annotation cp="↦">rightwards arrow from bar</annotation> + <annotation cp="↦" type="tts">rightwards arrow from bar</annotation> + <annotation cp="↧">downwards arrow from bar</annotation> + <annotation cp="↧" type="tts">downwards arrow from bar</annotation> + <annotation cp="↨">up down arrow with base</annotation> + <annotation cp="↨" type="tts">up down arrow with base</annotation> + <annotation cp="↫">leftwards arrow loop</annotation> + <annotation cp="↫" type="tts">leftwards arrow loop</annotation> + <annotation cp="↬">rightwards arrow loop</annotation> + <annotation cp="↬" type="tts">rightwards arrow loop</annotation> + <annotation cp="↭">left right wave arrow</annotation> + <annotation cp="↭" type="tts">left right wave arrow</annotation> + <annotation cp="↯">downwards zigzag arrow | zigzag</annotation> + <annotation cp="↯" type="tts">downwards zigzag arrow</annotation> + <annotation cp="↰">upwards arrow tip leftwards</annotation> + <annotation cp="↰" type="tts">upwards arrow tip leftwards</annotation> + <annotation cp="↱">upwards arrow tip rightwards</annotation> + <annotation cp="↱" type="tts">upwards arrow tip rightwards</annotation> + <annotation cp="↲">downwards arrow tip leftwards</annotation> + <annotation cp="↲" type="tts">downwards arrow tip leftwards</annotation> + <annotation cp="↳">downwards arrow tip rightwards</annotation> + <annotation cp="↳" type="tts">downwards arrow tip rightwards</annotation> + <annotation cp="↴">rightwards arrow corner downwards</annotation> + <annotation cp="↴" type="tts">rightwards arrow corner downwards</annotation> + <annotation cp="↵">downwards arrow corner leftwards</annotation> + <annotation cp="↵" type="tts">downwards arrow corner leftwards</annotation> + <annotation cp="↶">anticlockwise top semicircle arrow</annotation> + <annotation cp="↶" type="tts">anticlockwise top semicircle arrow</annotation> + <annotation cp="↷">clockwise top semicircle arrow</annotation> + <annotation cp="↷" type="tts">clockwise top semicircle arrow</annotation> + <annotation cp="↸">north west arrow long bar</annotation> + <annotation cp="↸" type="tts">north west arrow long bar</annotation> + <annotation cp="↹">leftwards arrow bar over rightwards arrow bar</annotation> + <annotation cp="↹" type="tts">leftwards arrow bar over rightwards arrow bar</annotation> + <annotation cp="↺">anticlockwise open circle arrow</annotation> + <annotation cp="↺" type="tts">anticlockwise open circle arrow</annotation> + <annotation cp="↻">clockwise open circle arrow</annotation> + <annotation cp="↻" type="tts">clockwise open circle arrow</annotation> + <annotation cp="↼">barb | leftwards harpoon barb upwards</annotation> + <annotation cp="↼" type="tts">leftwards harpoon barb upwards</annotation> + <annotation cp="↽">leftwards harpoon barb downwards</annotation> + <annotation cp="↽" type="tts">leftwards harpoon barb downwards</annotation> + <annotation cp="↾">barb | upwards harpoon barb rightwards</annotation> + <annotation cp="↾" type="tts">upwards harpoon barb rightwards</annotation> + <annotation cp="↿">barb | upwards harpoon barb leftwards</annotation> + <annotation cp="↿" type="tts">upwards harpoon barb leftwards</annotation> + <annotation cp="⇀">barb | rightwards harpoon barb upwards</annotation> + <annotation cp="⇀" type="tts">rightwards harpoon barb upwards</annotation> + <annotation cp="⇁">barb | rightwards harpoon barb downwards</annotation> + <annotation cp="⇁" type="tts">rightwards harpoon barb downwards</annotation> + <annotation cp="⇂">barb | downwards harpoon barb rightwards</annotation> + <annotation cp="⇂" type="tts">downwards harpoon barb rightwards</annotation> + <annotation cp="⇃">barb | downwards harpoon barb leftwards</annotation> + <annotation cp="⇃" type="tts">downwards harpoon barb leftwards</annotation> + <annotation cp="⇄">rightwards arrow over leftwards arrow</annotation> + <annotation cp="⇄" type="tts">rightwards arrow over leftwards arrow</annotation> + <annotation cp="⇅">arrow | down | up | up-pointing and down-pointing arrows</annotation> + <annotation cp="⇅" type="tts">up-pointing and down-pointing arrows</annotation> + <annotation cp="⇆">arrow | left | left-pointing over right-pointing arrows | right</annotation> + <annotation cp="⇆" type="tts">left-pointing over right-pointing arrows</annotation> + <annotation cp="⇇">leftwards paired arrows</annotation> + <annotation cp="⇇" type="tts">leftwards paired arrows</annotation> + <annotation cp="⇈">upwards paired arrows</annotation> + <annotation cp="⇈" type="tts">upwards paired arrows</annotation> + <annotation cp="⇉">rightwards paired arrows</annotation> + <annotation cp="⇉" type="tts">rightwards paired arrows</annotation> + <annotation cp="⇊">downwards paired arrows</annotation> + <annotation cp="⇊" type="tts">downwards paired arrows</annotation> + <annotation cp="⇋">leftwards harpoon over rightwards harpoon</annotation> + <annotation cp="⇋" type="tts">leftwards harpoon over rightwards harpoon</annotation> + <annotation cp="⇌">rightwards harpoon over leftwards harpoon</annotation> + <annotation cp="⇌" type="tts">rightwards harpoon over leftwards harpoon</annotation> + <annotation cp="⇐">leftwards double arrow</annotation> + <annotation cp="⇐" type="tts">leftwards double arrow</annotation> + <annotation cp="⇍">leftwards double arrow stroke</annotation> + <annotation cp="⇍" type="tts">leftwards double arrow stroke</annotation> + <annotation cp="⇑">upwards double arrow</annotation> + <annotation cp="⇑" type="tts">upwards double arrow</annotation> + <annotation cp="⇒">rightwards double arrow</annotation> + <annotation cp="⇒" type="tts">rightwards double arrow</annotation> + <annotation cp="⇏">rightwards double arrow stroke</annotation> + <annotation cp="⇏" type="tts">rightwards double arrow stroke</annotation> + <annotation cp="⇓">downwards double arrow</annotation> + <annotation cp="⇓" type="tts">downwards double arrow</annotation> + <annotation cp="⇔">left right double arrow</annotation> + <annotation cp="⇔" type="tts">left right double arrow</annotation> + <annotation cp="⇎">left right double arrow stroke</annotation> + <annotation cp="⇎" type="tts">left right double arrow stroke</annotation> + <annotation cp="⇖">north west double arrow</annotation> + <annotation cp="⇖" type="tts">north west double arrow</annotation> + <annotation cp="⇗">north east double arrow</annotation> + <annotation cp="⇗" type="tts">north east double arrow</annotation> + <annotation cp="⇘">south east double arrow</annotation> + <annotation cp="⇘" type="tts">south east double arrow</annotation> + <annotation cp="⇙">south west double arrow</annotation> + <annotation cp="⇙" type="tts">south west double arrow</annotation> + <annotation cp="⇚">leftwards triple arrow</annotation> + <annotation cp="⇚" type="tts">leftwards triple arrow</annotation> + <annotation cp="⇛">rightwards triple arrow</annotation> + <annotation cp="⇛" type="tts">rightwards triple arrow</annotation> + <annotation cp="⇜">leftwards squiggle arrow</annotation> + <annotation cp="⇜" type="tts">leftwards squiggle arrow</annotation> + <annotation cp="⇝">rightwards squiggle arrow</annotation> + <annotation cp="⇝" type="tts">rightwards squiggle arrow</annotation> + <annotation cp="⇞">upwards arrow double stroke</annotation> + <annotation cp="⇞" type="tts">upwards arrow double stroke</annotation> + <annotation cp="⇟">downwards arrow double stroke</annotation> + <annotation cp="⇟" type="tts">downwards arrow double stroke</annotation> + <annotation cp="⇠">leftwards dashed arrow</annotation> + <annotation cp="⇠" type="tts">leftwards dashed arrow</annotation> + <annotation cp="⇡">upwards dashed arrow</annotation> + <annotation cp="⇡" type="tts">upwards dashed arrow</annotation> + <annotation cp="⇢">rightwards dashed arrow</annotation> + <annotation cp="⇢" type="tts">rightwards dashed arrow</annotation> + <annotation cp="⇣">downwards dashed arrow</annotation> + <annotation cp="⇣" type="tts">downwards dashed arrow</annotation> + <annotation cp="⇤">leftwards arrow bar</annotation> + <annotation cp="⇤" type="tts">leftwards arrow bar</annotation> + <annotation cp="⇥">rightwards arrow bar</annotation> + <annotation cp="⇥" type="tts">rightwards arrow bar</annotation> + <annotation cp="⇦">leftwards hollow arrow</annotation> + <annotation cp="⇦" type="tts">leftwards hollow arrow</annotation> + <annotation cp="⇧">upwards hollow arrow</annotation> + <annotation cp="⇧" type="tts">upwards hollow arrow</annotation> + <annotation cp="⇨">rightwards hollow arrow</annotation> + <annotation cp="⇨" type="tts">rightwards hollow arrow</annotation> + <annotation cp="⇩">downwards hollow arrow</annotation> + <annotation cp="⇩" type="tts">downwards hollow arrow</annotation> + <annotation cp="⇪">upwards hollow arrow from bar</annotation> + <annotation cp="⇪" type="tts">upwards hollow arrow from bar</annotation> + <annotation cp="⇵">downwards arrow leftwards upwards arrow</annotation> + <annotation cp="⇵" type="tts">downwards arrow leftwards upwards arrow</annotation> + <annotation cp="∀">all | any | for all | given | universal</annotation> + <annotation cp="∀" type="tts">for all</annotation> + <annotation cp="∂">differential | partial differential</annotation> + <annotation cp="∂" type="tts">partial differential</annotation> + <annotation cp="∃">there exists</annotation> + <annotation cp="∃" type="tts">there exists</annotation> + <annotation cp="∅">empty set | mathematics | set operator</annotation> + <annotation cp="∅" type="tts">empty set</annotation> + <annotation cp="∆">increment | triangle</annotation> + <annotation cp="∆" type="tts">increment</annotation> + <annotation cp="∇">nabla | triangle</annotation> + <annotation cp="∇" type="tts">nabla</annotation> + <annotation cp="∈">contains | element | element of | membership | set</annotation> + <annotation cp="∈" type="tts">element of</annotation> + <annotation cp="∉">element | not an element</annotation> + <annotation cp="∉" type="tts">not an element</annotation> + <annotation cp="∋">contains as member | element</annotation> + <annotation cp="∋" type="tts">contains as member</annotation> + <annotation cp="∎">end proof | halmos | q.e.d. | qed | tombstone</annotation> + <annotation cp="∎" type="tts">end proof</annotation> + <annotation cp="∏">logic | n-ary product | product</annotation> + <annotation cp="∏" type="tts">n-ary product</annotation> + <annotation cp="∑">mathematics | n-ary summation | summation</annotation> + <annotation cp="∑" type="tts">n-ary summation</annotation> + <annotation cp="+">add | plus | plus sign</annotation> + <annotation cp="+" type="tts">plus sign</annotation> + <annotation cp="±">plus-minus</annotation> + <annotation cp="±" type="tts">plus-minus</annotation> + <annotation cp="÷">divide | division | division sign | obelus</annotation> + <annotation cp="÷" type="tts">division sign</annotation> + <annotation cp="×">multiplication | multiplication sign | multiply | times</annotation> + <annotation cp="×" type="tts">multiplication sign</annotation> + <annotation cp="<">less than | less-than | open tag | tag</annotation> + <annotation cp="<" type="tts">less-than</annotation> + <annotation cp="≮">inequality | mathematics | not less-than</annotation> + <annotation cp="≮" type="tts">not less-than</annotation> + <annotation cp="=">equal | equals</annotation> + <annotation cp="=" type="tts">equal</annotation> + <annotation cp="≠">inequality | inequation | not equal</annotation> + <annotation cp="≠" type="tts">not equal</annotation> + <annotation cp=">">close tag | greater than | greater-than | tag</annotation> + <annotation cp=">" type="tts">greater-than</annotation> + <annotation cp="≯">inequality | mathematics | not greater-than</annotation> + <annotation cp="≯" type="tts">not greater-than</annotation> + <annotation cp="¬">negation | not</annotation> + <annotation cp="¬" type="tts">negation</annotation> + <annotation cp="|">bar | line | pike | pipe | sheffer stroke | stick | stroke | vbar | vertical bar | vertical line</annotation> + <annotation cp="|" type="tts">vertical line</annotation> + <annotation cp="~">tilde</annotation> + <annotation cp="~" type="tts">tilde</annotation> + <annotation cp="−">minus | minus sign | subtract</annotation> + <annotation cp="−" type="tts">minus sign</annotation> + <annotation cp="⁻">minus | superscript</annotation> + <annotation cp="⁻" type="tts">superscript minus</annotation> + <annotation cp="∓">minus-or-plus | plus-minus</annotation> + <annotation cp="∓" type="tts">minus-or-plus</annotation> + <annotation cp="∕">division slash | slash | stroke | virgule</annotation> + <annotation cp="∕" type="tts">division slash</annotation> + <annotation cp="⁄">fraction slash | stroke | virgule</annotation> + <annotation cp="⁄" type="tts">fraction slash</annotation> + <annotation cp="∗">asterisk operator | star</annotation> + <annotation cp="∗" type="tts">asterisk operator</annotation> + <annotation cp="∘">composition | operator | ring operator</annotation> + <annotation cp="∘" type="tts">ring operator</annotation> + <annotation cp="∙">bullet operator | operator</annotation> + <annotation cp="∙" type="tts">bullet operator</annotation> + <annotation cp="√">radical | radix | root | square | surd</annotation> + <annotation cp="√" type="tts">square root</annotation> + <annotation cp="∝">proportional | proportionality</annotation> + <annotation cp="∝" type="tts">proportional</annotation> + <annotation cp="∞">infinity | infinity sign</annotation> + <annotation cp="∞" type="tts">infinity sign</annotation> + <annotation cp="∟">mathematics | right angle</annotation> + <annotation cp="∟" type="tts">right angle</annotation> + <annotation cp="∠">acute | angle</annotation> + <annotation cp="∠" type="tts">angle</annotation> + <annotation cp="∣">divides | divisor</annotation> + <annotation cp="∣" type="tts">divides</annotation> + <annotation cp="∥">parallel</annotation> + <annotation cp="∥" type="tts">parallel</annotation> + <annotation cp="∧">ac | atque | logical and | wedge</annotation> + <annotation cp="∧" type="tts">logical and</annotation> + <annotation cp="∩">intersection | set</annotation> + <annotation cp="∩" type="tts">intersection</annotation> + <annotation cp="∪">collection | set | union</annotation> + <annotation cp="∪" type="tts">union</annotation> + <annotation cp="∫">calculus | integral</annotation> + <annotation cp="∫" type="tts">integral</annotation> + <annotation cp="∬">calculus | double integral</annotation> + <annotation cp="∬" type="tts">double integral</annotation> + <annotation cp="∮">contour integral</annotation> + <annotation cp="∮" type="tts">contour integral</annotation> + <annotation cp="∴">logic | therefore</annotation> + <annotation cp="∴" type="tts">therefore</annotation> + <annotation cp="∵">because | logic</annotation> + <annotation cp="∵" type="tts">because</annotation> + <annotation cp="∶">ratio</annotation> + <annotation cp="∶" type="tts">ratio</annotation> + <annotation cp="∷">proportion | proportionality</annotation> + <annotation cp="∷" type="tts">proportion</annotation> + <annotation cp="∼">operator | tilde operator</annotation> + <annotation cp="∼" type="tts">tilde operator</annotation> + <annotation cp="∽">reversed tilde | tilde</annotation> + <annotation cp="∽" type="tts">reversed tilde</annotation> + <annotation cp="∾">inverted lazy s</annotation> + <annotation cp="∾" type="tts">inverted lazy s</annotation> + <annotation cp="≃">asymptote | asymptotically equal | mathematics</annotation> + <annotation cp="≃" type="tts">asymptotically equal</annotation> + <annotation cp="≅">approximately equal | congruence | equality | isomorphism | mathematics</annotation> + <annotation cp="≅" type="tts">approximately equal</annotation> + <annotation cp="≈">almost equal | approximate | approximation</annotation> + <annotation cp="≈" type="tts">almost equal</annotation> + <annotation cp="≌">all equal | equality | mathematics</annotation> + <annotation cp="≌" type="tts">all equal</annotation> + <annotation cp="≒">approximately equal the image</annotation> + <annotation cp="≒" type="tts">approximately equal the image</annotation> + <annotation cp="≖">equality | mathematics | ring in equal</annotation> + <annotation cp="≖" type="tts">ring in equal</annotation> + <annotation cp="≡">exact | identical | identical to | triple</annotation> + <annotation cp="≡" type="tts">identical to</annotation> + <annotation cp="≣">equality | mathematics | strictly equivalent</annotation> + <annotation cp="≣" type="tts">strictly equivalent</annotation> + <annotation cp="≤">equal | equals | inequality | less-than or equal | less-then</annotation> + <annotation cp="≤" type="tts">less-than or equal</annotation> + <annotation cp="≥">equal | equals | greater-than | greater-than or equal | inequality</annotation> + <annotation cp="≥" type="tts">greater-than or equal</annotation> + <annotation cp="≦">inequality | less-than over equal | mathematics</annotation> + <annotation cp="≦" type="tts">less-than over equal</annotation> + <annotation cp="≧">greater-than over equal | inequality | mathematics</annotation> + <annotation cp="≧" type="tts">greater-than over equal</annotation> + <annotation cp="≪">inequality | mathematics | much less-than</annotation> + <annotation cp="≪" type="tts">much less-than</annotation> + <annotation cp="≫">inequality | mathematics | much greater-than</annotation> + <annotation cp="≫" type="tts">much greater-than</annotation> + <annotation cp="≬">between</annotation> + <annotation cp="≬" type="tts">between</annotation> + <annotation cp="≳">greater-than equivalent | inequality | mathematics</annotation> + <annotation cp="≳" type="tts">greater-than equivalent</annotation> + <annotation cp="≺">mathematics | precedes | set operator</annotation> + <annotation cp="≺" type="tts">precedes</annotation> + <annotation cp="≻">mathematics | set operator | succeeds</annotation> + <annotation cp="≻" type="tts">succeeds</annotation> + <annotation cp="⊁">does not succeed | mathematics | set operator</annotation> + <annotation cp="⊁" type="tts">does not succeed</annotation> + <annotation cp="⊂">set | subset | subset of</annotation> + <annotation cp="⊂" type="tts">subset of</annotation> + <annotation cp="⊃">mathematics | set operator | superset</annotation> + <annotation cp="⊃" type="tts">superset</annotation> + <annotation cp="⊆">subset equal</annotation> + <annotation cp="⊆" type="tts">subset equal</annotation> + <annotation cp="⊇">equality | mathematics | superset equal</annotation> + <annotation cp="⊇" type="tts">superset equal</annotation> + <annotation cp="⊕">circled plus | plus</annotation> + <annotation cp="⊕" type="tts">circled plus</annotation> + <annotation cp="⊖">circled minus | erosion | symmetric difference</annotation> + <annotation cp="⊖" type="tts">circled minus</annotation> + <annotation cp="⊗">circled times | product | tensor</annotation> + <annotation cp="⊗" type="tts">circled times</annotation> + <annotation cp="⊘">circled division slash | division-like | mathematics</annotation> + <annotation cp="⊘" type="tts">circled division slash</annotation> + <annotation cp="⊙">circled dot operator | operator | XNOR</annotation> + <annotation cp="⊙" type="tts">circled dot operator</annotation> + <annotation cp="⊚">circled ring operator</annotation> + <annotation cp="⊚" type="tts">circled ring operator</annotation> + <annotation cp="⊛">asterisk | circled asterisk operator | operator</annotation> + <annotation cp="⊛" type="tts">circled asterisk operator</annotation> + <annotation cp="⊞">addition-like | mathematics | squared plus</annotation> + <annotation cp="⊞" type="tts">squared plus</annotation> + <annotation cp="⊟">mathematics | squared minus | subtraction-like</annotation> + <annotation cp="⊟" type="tts">squared minus</annotation> + <annotation cp="⊥">eet | falsum | tack | up tack</annotation> + <annotation cp="⊥" type="tts">up tack</annotation> + <annotation cp="⊮">does not force</annotation> + <annotation cp="⊮" type="tts">does not force</annotation> + <annotation cp="⊰">mathematics | precedes under relation | set operator</annotation> + <annotation cp="⊰" type="tts">precedes under relation</annotation> + <annotation cp="⊱">mathematics | set operator | succeeds under relation</annotation> + <annotation cp="⊱" type="tts">succeeds under relation</annotation> + <annotation cp="⋭">does not contain as normal subgroup equal | group theory | mathematics</annotation> + <annotation cp="⋭" type="tts">does not contain as normal subgroup equal</annotation> + <annotation cp="⊶">original</annotation> + <annotation cp="⊶" type="tts">original</annotation> + <annotation cp="⊹">hermitian conjugate matrix | mathematics | self-adjoint matrix | square matrix</annotation> + <annotation cp="⊹" type="tts">hermitian conjugate matrix</annotation> + <annotation cp="⊿">mathematics | right triangle | right-angled triangle</annotation> + <annotation cp="⊿" type="tts">right triangle</annotation> + <annotation cp="⋁">disjunction | logic | n-ary logical or</annotation> + <annotation cp="⋁" type="tts">n-ary logical or</annotation> + <annotation cp="⋂">intersection | mathematics | n-ary intersection | set operator</annotation> + <annotation cp="⋂" type="tts">n-ary intersection</annotation> + <annotation cp="⋃">mathematics | n-ary union | set operator | union</annotation> + <annotation cp="⋃" type="tts">n-ary union</annotation> + <annotation cp="⋅">dot operator | operator</annotation> + <annotation cp="⋅" type="tts">dot operator</annotation> + <annotation cp="⋆">operator | star operator</annotation> + <annotation cp="⋆" type="tts">star operator</annotation> + <annotation cp="⋈">binary operator | bowtie | natural join</annotation> + <annotation cp="⋈" type="tts">natural join</annotation> + <annotation cp="⋒">double intersection | intersection | mathematics | set operator</annotation> + <annotation cp="⋒" type="tts">double intersection</annotation> + <annotation cp="⋘">inequality | mathematics | very much less-than</annotation> + <annotation cp="⋘" type="tts">very much less-than</annotation> + <annotation cp="⋙">inequality | mathematics | very much greater-than</annotation> + <annotation cp="⋙" type="tts">very much greater-than</annotation> + <annotation cp="⋮">ellipsis | mathematics | vertical ellipsis</annotation> + <annotation cp="⋮" type="tts">vertical ellipsis</annotation> + <annotation cp="⋯">ellipsis | midline horizontal ellipsis</annotation> + <annotation cp="⋯" type="tts">midline horizontal ellipsis</annotation> + <annotation cp="⋰">ellipsis | mathematics | up right diagonal ellipsis</annotation> + <annotation cp="⋰" type="tts">up right diagonal ellipsis</annotation> + <annotation cp="⋱">down right diagonal ellipsis | ellipsis | mathematics</annotation> + <annotation cp="⋱" type="tts">down right diagonal ellipsis</annotation> + <annotation cp="■">filled square</annotation> + <annotation cp="■" type="tts">filled square</annotation> + <annotation cp="□">hollow square</annotation> + <annotation cp="□" type="tts">hollow square</annotation> + <annotation cp="▢">hollow square with rounded corners</annotation> + <annotation cp="▢" type="tts">hollow square with rounded corners</annotation> + <annotation cp="▣">hollow square containing filled square</annotation> + <annotation cp="▣" type="tts">hollow square containing filled square</annotation> + <annotation cp="▤">square with horizontal fill</annotation> + <annotation cp="▤" type="tts">square with horizontal fill</annotation> + <annotation cp="▥">square with vertical fill</annotation> + <annotation cp="▥" type="tts">square with vertical fill</annotation> + <annotation cp="▦">square orthogonal crosshatch fill</annotation> + <annotation cp="▦" type="tts">square orthogonal crosshatch fill</annotation> + <annotation cp="▧">square upper left lower right fill</annotation> + <annotation cp="▧" type="tts">square upper left lower right fill</annotation> + <annotation cp="▨">square upper right lower left fill</annotation> + <annotation cp="▨" type="tts">square upper right lower left fill</annotation> + <annotation cp="▩">square diagonal crosshatch fill</annotation> + <annotation cp="▩" type="tts">square diagonal crosshatch fill</annotation> + <annotation cp="▬">filled rectangle</annotation> + <annotation cp="▬" type="tts">filled rectangle</annotation> + <annotation cp="▭">hollow rectangle</annotation> + <annotation cp="▭" type="tts">hollow rectangle</annotation> + <annotation cp="▮">filled vertical rectangle</annotation> + <annotation cp="▮" type="tts">filled vertical rectangle</annotation> + <annotation cp="▰">filled parallelogram</annotation> + <annotation cp="▰" type="tts">filled parallelogram</annotation> + <annotation cp="▲">arrow | filled | filled up-pointing triangle | triangle | up</annotation> + <annotation cp="▲" type="tts">filled up-pointing triangle</annotation> + <annotation cp="△">hollow up-pointing triangle</annotation> + <annotation cp="△" type="tts">hollow up-pointing triangle</annotation> + <annotation cp="▴">filled up-pointing small triangle</annotation> + <annotation cp="▴" type="tts">filled up-pointing small triangle</annotation> + <annotation cp="▵">hollow up-pointing small triangle</annotation> + <annotation cp="▵" type="tts">hollow up-pointing small triangle</annotation> + <annotation cp="▷">hollow right-pointing triangle</annotation> + <annotation cp="▷" type="tts">hollow right-pointing triangle</annotation> + <annotation cp="▸">filled right-pointing small triangle</annotation> + <annotation cp="▸" type="tts">filled right-pointing small triangle</annotation> + <annotation cp="▹">hollow right-pointing small triangle</annotation> + <annotation cp="▹" type="tts">hollow right-pointing small triangle</annotation> + <annotation cp="►">filled right-pointing pointer</annotation> + <annotation cp="►" type="tts">filled right-pointing pointer</annotation> + <annotation cp="▻">hollow right-pointing pointer</annotation> + <annotation cp="▻" type="tts">hollow right-pointing pointer</annotation> + <annotation cp="▼">arrow | down | filled | filled down-pointing triangle | triangle</annotation> + <annotation cp="▼" type="tts">filled down-pointing triangle</annotation> + <annotation cp="▽">hollow down-pointing triangle</annotation> + <annotation cp="▽" type="tts">hollow down-pointing triangle</annotation> + <annotation cp="▾">filled down-pointing small triangle</annotation> + <annotation cp="▾" type="tts">filled down-pointing small triangle</annotation> + <annotation cp="▿">hollow down-pointing small triangle</annotation> + <annotation cp="▿" type="tts">hollow down-pointing small triangle</annotation> + <annotation cp="◁">hollow left-pointing triangle</annotation> + <annotation cp="◁" type="tts">hollow left-pointing triangle</annotation> + <annotation cp="◂">filled left-pointing small triangle</annotation> + <annotation cp="◂" type="tts">filled left-pointing small triangle</annotation> + <annotation cp="◃">hollow left-pointing small triangle</annotation> + <annotation cp="◃" type="tts">hollow left-pointing small triangle</annotation> + <annotation cp="◄">filled left-pointing pointer</annotation> + <annotation cp="◄" type="tts">filled left-pointing pointer</annotation> + <annotation cp="◅">hollow left-pointing pointer</annotation> + <annotation cp="◅" type="tts">hollow left-pointing pointer</annotation> + <annotation cp="◆">filled diamond</annotation> + <annotation cp="◆" type="tts">filled diamond</annotation> + <annotation cp="◇">hollow diamond</annotation> + <annotation cp="◇" type="tts">hollow diamond</annotation> + <annotation cp="◈">hollow diamond containing filled diamond</annotation> + <annotation cp="◈" type="tts">hollow diamond containing filled diamond</annotation> + <annotation cp="◉">circled dot | concentric cicles filled | fisheye | hollow circle containing filled circle | ward</annotation> + <annotation cp="◉" type="tts">hollow circle containing filled circle</annotation> + <annotation cp="◊">diamond | lozenge | rhombus</annotation> + <annotation cp="◊" type="tts">lozenge</annotation> + <annotation cp="○">circle | hollow circle | ring</annotation> + <annotation cp="○" type="tts">hollow circle</annotation> + <annotation cp="◌">dotted circle</annotation> + <annotation cp="◌" type="tts">dotted circle</annotation> + <annotation cp="◍">circle with vertical fill</annotation> + <annotation cp="◍" type="tts">circle with vertical fill</annotation> + <annotation cp="◎">concentric circles | double circle | target</annotation> + <annotation cp="◎" type="tts">concentric circles</annotation> + <annotation cp="●">circle | filled circle</annotation> + <annotation cp="●" type="tts">filled circle</annotation> + <annotation cp="◐">circle left half filled</annotation> + <annotation cp="◐" type="tts">circle left half filled</annotation> + <annotation cp="◑">circle right half filled</annotation> + <annotation cp="◑" type="tts">circle right half filled</annotation> + <annotation cp="◒">circle lower half filled</annotation> + <annotation cp="◒" type="tts">circle lower half filled</annotation> + <annotation cp="◓">circle upper half filled</annotation> + <annotation cp="◓" type="tts">circle upper half filled</annotation> + <annotation cp="◔">circle upper right quadrant filled</annotation> + <annotation cp="◔" type="tts">circle upper right quadrant filled</annotation> + <annotation cp="◕">circle all but upper left quadrant filled</annotation> + <annotation cp="◕" type="tts">circle all but upper left quadrant filled</annotation> + <annotation cp="◖">left half filled circle</annotation> + <annotation cp="◖" type="tts">left half filled circle</annotation> + <annotation cp="◗">right half filled circle</annotation> + <annotation cp="◗" type="tts">right half filled circle</annotation> + <annotation cp="◘">inverse bullet</annotation> + <annotation cp="◘" type="tts">inverse bullet</annotation> + <annotation cp="◙">filled square containing hollow circle | inverse hollow circle</annotation> + <annotation cp="◙" type="tts">filled square containing hollow circle</annotation> + <annotation cp="◜">upper left quadrant circular arc</annotation> + <annotation cp="◜" type="tts">upper left quadrant circular arc</annotation> + <annotation cp="◝">upper right quadrant circular arc</annotation> + <annotation cp="◝" type="tts">upper right quadrant circular arc</annotation> + <annotation cp="◞">lower right quadrant circular arc</annotation> + <annotation cp="◞" type="tts">lower right quadrant circular arc</annotation> + <annotation cp="◟">lower left quadrant circular arc</annotation> + <annotation cp="◟" type="tts">lower left quadrant circular arc</annotation> + <annotation cp="◠">upper half circle</annotation> + <annotation cp="◠" type="tts">upper half circle</annotation> + <annotation cp="◡">lower half circle</annotation> + <annotation cp="◡" type="tts">lower half circle</annotation> + <annotation cp="◢">filled lower right triangle</annotation> + <annotation cp="◢" type="tts">filled lower right triangle</annotation> + <annotation cp="◣">filled lower left triangle</annotation> + <annotation cp="◣" type="tts">filled lower left triangle</annotation> + <annotation cp="◤">filled upper left triangle</annotation> + <annotation cp="◤" type="tts">filled upper left triangle</annotation> + <annotation cp="◥">filled upper right triangle</annotation> + <annotation cp="◥" type="tts">filled upper right triangle</annotation> + <annotation cp="◦">hollow bullet</annotation> + <annotation cp="◦" type="tts">hollow bullet</annotation> + <annotation cp="◯">circle | large hollow circle | ring</annotation> + <annotation cp="◯" type="tts">large hollow circle</annotation> + <annotation cp="◳">hollow square upper right quadrant</annotation> + <annotation cp="◳" type="tts">hollow square upper right quadrant</annotation> + <annotation cp="◷">hollow circle with upper right quadrant</annotation> + <annotation cp="◷" type="tts">hollow circle with upper right quadrant</annotation> + <annotation cp="◿">lower right triangle</annotation> + <annotation cp="◿" type="tts">lower right triangle</annotation> + <annotation cp="♪">eighth | music | note</annotation> + <annotation cp="♪" type="tts">eighth note</annotation> + <annotation cp="⨧">plus subscript two</annotation> + <annotation cp="⨧" type="tts">plus subscript two</annotation> + <annotation cp="⨯">vector cross product</annotation> + <annotation cp="⨯" type="tts">vector cross product</annotation> + <annotation cp="⨼">interior product</annotation> + <annotation cp="⨼" type="tts">interior product</annotation> + <annotation cp="⩣">logical or double underbar</annotation> + <annotation cp="⩣" type="tts">logical or double underbar</annotation> + <annotation cp="⩽">less-than slanted equal</annotation> + <annotation cp="⩽" type="tts">less-than slanted equal</annotation> + <annotation cp="⪍">less-than above similar equal</annotation> + <annotation cp="⪍" type="tts">less-than above similar equal</annotation> + <annotation cp="⪚">double-line equal greater-than | inequality | mathematics</annotation> + <annotation cp="⪚" type="tts">double-line equal greater-than</annotation> + <annotation cp="⪺">succeeds above not almost equal</annotation> + <annotation cp="⪺" type="tts">succeeds above not almost equal</annotation> + <annotation cp="♭">bemolle | flat | music | note</annotation> + <annotation cp="♭" type="tts">flat</annotation> + <annotation cp="♯">dièse | diesis | music | note | sharp</annotation> + <annotation cp="♯" type="tts">sharp</annotation> + <annotation cp="😀">face | grin | grinning face</annotation> + <annotation cp="😀" type="tts">grinning face</annotation> + <annotation cp="😃">face | grinning face with big eyes | mouth | open | smile</annotation> + <annotation cp="😃" type="tts">grinning face with big eyes</annotation> + <annotation cp="😄">eye | face | grinning face with smiling eyes | mouth | open | smile</annotation> + <annotation cp="😄" type="tts">grinning face with smiling eyes</annotation> + <annotation cp="😁">beaming face with smiling eyes | eye | face | grin | smile</annotation> + <annotation cp="😁" type="tts">beaming face with smiling eyes</annotation> + <annotation cp="😆">face | grinning squinting face | laugh | mouth | satisfied | smile</annotation> + <annotation cp="😆" type="tts">grinning squinting face</annotation> + <annotation cp="😅">cold | face | grinning face with sweat | open | smile | sweat</annotation> + <annotation cp="😅" type="tts">grinning face with sweat</annotation> + <annotation cp="🤣">face | floor | laugh | rofl | rolling | rolling on the floor laughing | rotfl</annotation> + <annotation cp="🤣" type="tts">rolling on the floor laughing</annotation> + <annotation cp="😂">face | face with tears of joy | joy | laugh | tear</annotation> + <annotation cp="😂" type="tts">face with tears of joy</annotation> + <annotation cp="🙂">face | slightly smiling face | smile</annotation> + <annotation cp="🙂" type="tts">slightly smiling face</annotation> + <annotation cp="🙃">face | upside-down</annotation> + <annotation cp="🙃" type="tts">upside-down face</annotation> + <annotation cp="😉">face | wink | winking face</annotation> + <annotation cp="😉" type="tts">winking face</annotation> + <annotation cp="😊">blush | eye | face | smile | smiling face with smiling eyes</annotation> + <annotation cp="😊" type="tts">smiling face with smiling eyes</annotation> + <annotation cp="😇">angel | face | fantasy | halo | innocent | smiling face with halo</annotation> + <annotation cp="😇" type="tts">smiling face with halo</annotation> + <annotation cp="🥰">adore | crush | hearts | in love | smiling face with hearts</annotation> + <annotation cp="🥰" type="tts">smiling face with hearts</annotation> + <annotation cp="😍">eye | face | love | smile | smiling face with heart-eyes</annotation> + <annotation cp="😍" type="tts">smiling face with heart-eyes</annotation> + <annotation cp="🤩">eyes | face | grinning | star | star-struck</annotation> + <annotation cp="🤩" type="tts">star-struck</annotation> + <annotation cp="😘">face | face blowing a kiss | kiss</annotation> + <annotation cp="😘" type="tts">face blowing a kiss</annotation> + <annotation cp="😗">face | kiss | kissing face</annotation> + <annotation cp="😗" type="tts">kissing face</annotation> + <annotation cp="☺">face | outlined | relaxed | smile | smiling face</annotation> + <annotation cp="☺" type="tts">smiling face</annotation> + <annotation cp="😚">closed | eye | face | kiss | kissing face with closed eyes</annotation> + <annotation cp="😚" type="tts">kissing face with closed eyes</annotation> + <annotation cp="😙">eye | face | kiss | kissing face with smiling eyes | smile</annotation> + <annotation cp="😙" type="tts">kissing face with smiling eyes</annotation> + <annotation cp="🥲">grateful | proud | relieved | smiling | smiling face with tear | tear | touched</annotation> + <annotation cp="🥲" type="tts">smiling face with tear</annotation> + <annotation cp="😋">delicious | face | face savoring food | savouring | smile | yum</annotation> + <annotation cp="😋" type="tts">face savoring food</annotation> + <annotation cp="😛">face | face with tongue | tongue</annotation> + <annotation cp="😛" type="tts">face with tongue</annotation> + <annotation cp="😜">eye | face | joke | tongue | wink | winking face with tongue</annotation> + <annotation cp="😜" type="tts">winking face with tongue</annotation> + <annotation cp="🤪">eye | goofy | large | small | zany face</annotation> + <annotation cp="🤪" type="tts">zany face</annotation> + <annotation cp="😝">eye | face | horrible | squinting face with tongue | taste | tongue</annotation> + <annotation cp="😝" type="tts">squinting face with tongue</annotation> + <annotation cp="🤑">face | money | money-mouth face | mouth</annotation> + <annotation cp="🤑" type="tts">money-mouth face</annotation> + <annotation cp="🤗">face | hug | hugging</annotation> + <annotation cp="🤗" type="tts">hugging face</annotation> + <annotation cp="🤭">face with hand over mouth | whoops</annotation> + <annotation cp="🤭" type="tts">face with hand over mouth</annotation> + <annotation cp="🤫">quiet | shush | shushing face</annotation> + <annotation cp="🤫" type="tts">shushing face</annotation> + <annotation cp="🤔">face | thinking</annotation> + <annotation cp="🤔" type="tts">thinking face</annotation> + <annotation cp="🤐">face | mouth | zipper | zipper-mouth face</annotation> + <annotation cp="🤐" type="tts">zipper-mouth face</annotation> + <annotation cp="🤨">distrust | face with raised eyebrow | skeptic</annotation> + <annotation cp="🤨" type="tts">face with raised eyebrow</annotation> + <annotation cp="😐">deadpan | face | meh | neutral</annotation> + <annotation cp="😐" type="tts">neutral face</annotation> + <annotation cp="😑">expressionless | face | inexpressive | meh | unexpressive</annotation> + <annotation cp="😑" type="tts">expressionless face</annotation> + <annotation cp="😶">face | face without mouth | mouth | quiet | silent</annotation> + <annotation cp="😶" type="tts">face without mouth</annotation> + <annotation cp="😶🌫">absentminded | face in clouds | face in the fog | head in clouds</annotation> <!-- 1F636 200D 1F32B FE0F: face in clouds --> + <annotation cp="😶🌫" type="tts">face in clouds</annotation> + <annotation cp="😏">face | smirk | smirking face</annotation> + <annotation cp="😏" type="tts">smirking face</annotation> + <annotation cp="😒">face | unamused | unhappy</annotation> + <annotation cp="😒" type="tts">unamused face</annotation> + <annotation cp="🙄">eyeroll | eyes | face | face with rolling eyes | rolling</annotation> + <annotation cp="🙄" type="tts">face with rolling eyes</annotation> + <annotation cp="😬">face | grimace | grimacing face</annotation> + <annotation cp="😬" type="tts">grimacing face</annotation> + <annotation cp="😮💨">exhale | face exhaling | gasp | groan | relief | whisper | whistle</annotation> <!-- 1F62E 200D 1F4A8 --> + <annotation cp="😮💨" type="tts">face exhaling</annotation> + <annotation cp="🤥">face | lie | lying face | pinocchio</annotation> + <annotation cp="🤥" type="tts">lying face</annotation> + <annotation cp="😌">face | relieved</annotation> + <annotation cp="😌" type="tts">relieved face</annotation> + <annotation cp="😔">dejected | face | pensive</annotation> + <annotation cp="😔" type="tts">pensive face</annotation> + <annotation cp="😪">face | sleep | sleepy face</annotation> + <annotation cp="😪" type="tts">sleepy face</annotation> + <annotation cp="🤤">drooling | face</annotation> + <annotation cp="🤤" type="tts">drooling face</annotation> + <annotation cp="😴">face | sleep | sleeping face | zzz</annotation> + <annotation cp="😴" type="tts">sleeping face</annotation> + <annotation cp="😷">cold | doctor | face | face with medical mask | mask | sick</annotation> + <annotation cp="😷" type="tts">face with medical mask</annotation> + <annotation cp="🤒">face | face with thermometer | ill | sick | thermometer</annotation> + <annotation cp="🤒" type="tts">face with thermometer</annotation> + <annotation cp="🤕">bandage | face | face with head-bandage | hurt | injury</annotation> + <annotation cp="🤕" type="tts">face with head-bandage</annotation> + <annotation cp="🤢">face | nauseated | vomit</annotation> + <annotation cp="🤢" type="tts">nauseated face</annotation> + <annotation cp="🤮">face vomiting | sick | vomit | puke</annotation> + <annotation cp="🤮" type="tts">face vomiting</annotation> + <annotation cp="🤧">face | gesundheit | sneeze | sneezing face</annotation> + <annotation cp="🤧" type="tts">sneezing face</annotation> + <annotation cp="🥵">feverish | heat stroke | hot | hot face | red-faced | sweating</annotation> + <annotation cp="🥵" type="tts">hot face</annotation> + <annotation cp="🥶">blue-faced | cold | cold face | freezing | frostbite | icicles</annotation> + <annotation cp="🥶" type="tts">cold face</annotation> + <annotation cp="🥴">dizzy | intoxicated | tipsy | uneven eyes | wavy mouth | woozy face</annotation> + <annotation cp="🥴" type="tts">woozy face</annotation> + <annotation cp="😵">dead | face | knocked out | knocked-out face</annotation> + <annotation cp="😵" type="tts">knocked-out face</annotation> + <annotation cp="😵💫">dizzy | face with spiral eyes | hypnotized | spiral | trouble | whoa</annotation> <!-- 1F635 200D 1F4AB --> + <annotation cp="😵💫" type="tts">face with spiral eyes</annotation> + <annotation cp="🤯">exploding head | mind blown | shocked</annotation> + <annotation cp="🤯" type="tts">exploding head</annotation> + <annotation cp="🤠">cowboy | cowgirl | face | hat</annotation> + <annotation cp="🤠" type="tts">cowboy hat face</annotation> + <annotation cp="🥳">celebration | hat | horn | party | partying face</annotation> + <annotation cp="🥳" type="tts">partying face</annotation> + <annotation cp="🥸">disguise | disguised face | face | glasses | incognito | nose</annotation> + <annotation cp="🥸" type="tts">disguised face</annotation> + <annotation cp="😎">bright | cool | face | smiling face with sunglasses | sun | sunglasses</annotation> + <annotation cp="😎" type="tts">smiling face with sunglasses</annotation> + <annotation cp="🤓">face | geek | nerd</annotation> + <annotation cp="🤓" type="tts">nerd face</annotation> + <annotation cp="🧐">face with monocle | stuffy</annotation> + <annotation cp="🧐" type="tts">face with monocle</annotation> + <annotation cp="😕">confused | face | meh</annotation> + <annotation cp="😕" type="tts">confused face</annotation> + <annotation cp="😟">face | worried</annotation> + <annotation cp="😟" type="tts">worried face</annotation> + <annotation cp="🙁">face | frown | slightly frowning face</annotation> + <annotation cp="🙁" type="tts">slightly frowning face</annotation> + <annotation cp="☹">face | frown | frowning face</annotation> + <annotation cp="☹" type="tts">frowning face</annotation> + <annotation cp="😮">face | face with open mouth | mouth | open | sympathy</annotation> + <annotation cp="😮" type="tts">face with open mouth</annotation> + <annotation cp="😯">face | hushed | stunned | surprised</annotation> + <annotation cp="😯" type="tts">hushed face</annotation> + <annotation cp="😲">astonished | face | shocked | totally</annotation> + <annotation cp="😲" type="tts">astonished face</annotation> + <annotation cp="😳">dazed | face | flushed</annotation> + <annotation cp="😳" type="tts">flushed face</annotation> + <annotation cp="🥺">begging | mercy | pleading face | puppy eyes</annotation> + <annotation cp="🥺" type="tts">pleading face</annotation> + <annotation cp="😦">face | frown | frowning face with open mouth | mouth | open</annotation> + <annotation cp="😦" type="tts">frowning face with open mouth</annotation> + <annotation cp="😧">anguished | face</annotation> + <annotation cp="😧" type="tts">anguished face</annotation> + <annotation cp="😨">face | fear | fearful | scared</annotation> + <annotation cp="😨" type="tts">fearful face</annotation> + <annotation cp="😰">anxious face with sweat | blue | cold | face | rushed | sweat</annotation> + <annotation cp="😰" type="tts">anxious face with sweat</annotation> + <annotation cp="😥">disappointed | face | relieved | sad but relieved face | whew</annotation> + <annotation cp="😥" type="tts">sad but relieved face</annotation> + <annotation cp="😢">cry | crying face | face | sad | tear</annotation> + <annotation cp="😢" type="tts">crying face</annotation> + <annotation cp="😭">cry | face | loudly crying face | sad | sob | tear</annotation> + <annotation cp="😭" type="tts">loudly crying face</annotation> + <annotation cp="😱">face | face screaming in fear | fear | munch | scared | scream</annotation> + <annotation cp="😱" type="tts">face screaming in fear</annotation> + <annotation cp="😖">confounded | face</annotation> + <annotation cp="😖" type="tts">confounded face</annotation> + <annotation cp="😣">face | persevere | persevering face</annotation> + <annotation cp="😣" type="tts">persevering face</annotation> + <annotation cp="😞">disappointed | face</annotation> + <annotation cp="😞" type="tts">disappointed face</annotation> + <annotation cp="😓">cold | downcast face with sweat | face | sweat</annotation> + <annotation cp="😓" type="tts">downcast face with sweat</annotation> + <annotation cp="😩">face | tired | weary</annotation> + <annotation cp="😩" type="tts">weary face</annotation> + <annotation cp="😫">face | tired</annotation> + <annotation cp="😫" type="tts">tired face</annotation> + <annotation cp="🥱">bored | tired | yawn | yawning face</annotation> + <annotation cp="🥱" type="tts">yawning face</annotation> + <annotation cp="😤">face | face with steam from nose | triumph | won</annotation> + <annotation cp="😤" type="tts">face with steam from nose</annotation> + <annotation cp="😡">angry | face | mad | pouting | rage | red</annotation> + <annotation cp="😡" type="tts">pouting face</annotation> + <annotation cp="😠">angry | face | mad | anger</annotation> + <annotation cp="😠" type="tts">angry face</annotation> + <annotation cp="🤬">face with symbols on mouth | swearing</annotation> + <annotation cp="🤬" type="tts">face with symbols on mouth</annotation> + <annotation cp="😈">face | fairy tale | fantasy | horns | smile | smiling face with horns</annotation> + <annotation cp="😈" type="tts">smiling face with horns</annotation> + <annotation cp="👿">angry face with horns | demon | devil | face | fantasy | imp</annotation> + <annotation cp="👿" type="tts">angry face with horns</annotation> + <annotation cp="💀">death | face | fairy tale | monster | skull</annotation> + <annotation cp="💀" type="tts">skull</annotation> + <annotation cp="☠">crossbones | death | face | monster | skull | skull and crossbones</annotation> + <annotation cp="☠" type="tts">skull and crossbones</annotation> + <annotation cp="💩">dung | face | monster | pile of poo | poo | poop</annotation> + <annotation cp="💩" type="tts">pile of poo</annotation> + <annotation cp="🤡">clown | face</annotation> + <annotation cp="🤡" type="tts">clown face</annotation> + <annotation cp="👹">creature | face | fairy tale | fantasy | monster | ogre</annotation> + <annotation cp="👹" type="tts">ogre</annotation> + <annotation cp="👺">creature | face | fairy tale | fantasy | goblin | monster</annotation> + <annotation cp="👺" type="tts">goblin</annotation> + <annotation cp="👻">creature | face | fairy tale | fantasy | ghost | monster</annotation> + <annotation cp="👻" type="tts">ghost</annotation> + <annotation cp="👽">alien | creature | extraterrestrial | face | fantasy | ufo</annotation> + <annotation cp="👽" type="tts">alien</annotation> + <annotation cp="👾">alien | creature | extraterrestrial | face | monster | ufo</annotation> + <annotation cp="👾" type="tts">alien monster</annotation> + <annotation cp="🤖">face | monster | robot</annotation> + <annotation cp="🤖" type="tts">robot</annotation> + <annotation cp="😺">cat | face | grinning | mouth | open | smile</annotation> + <annotation cp="😺" type="tts">grinning cat</annotation> + <annotation cp="😸">cat | eye | face | grin | grinning cat with smiling eyes | smile</annotation> + <annotation cp="😸" type="tts">grinning cat with smiling eyes</annotation> + <annotation cp="😹">cat | cat with tears of joy | face | joy | tear</annotation> + <annotation cp="😹" type="tts">cat with tears of joy</annotation> + <annotation cp="😻">cat | eye | face | heart | love | smile | smiling cat with heart-eyes</annotation> + <annotation cp="😻" type="tts">smiling cat with heart-eyes</annotation> + <annotation cp="😼">cat | cat with wry smile | face | ironic | smile | wry</annotation> + <annotation cp="😼" type="tts">cat with wry smile</annotation> + <annotation cp="😽">cat | eye | face | kiss | kissing cat</annotation> + <annotation cp="😽" type="tts">kissing cat</annotation> + <annotation cp="🙀">cat | face | oh | surprised | weary</annotation> + <annotation cp="🙀" type="tts">weary cat</annotation> + <annotation cp="😿">cat | cry | crying cat | face | sad | tear</annotation> + <annotation cp="😿" type="tts">crying cat</annotation> + <annotation cp="😾">cat | face | pouting</annotation> + <annotation cp="😾" type="tts">pouting cat</annotation> + <annotation cp="🙈">evil | face | forbidden | monkey | see | see-no-evil monkey</annotation> + <annotation cp="🙈" type="tts">see-no-evil monkey</annotation> + <annotation cp="🙉">evil | face | forbidden | hear | hear-no-evil monkey | monkey</annotation> + <annotation cp="🙉" type="tts">hear-no-evil monkey</annotation> + <annotation cp="🙊">evil | face | forbidden | monkey | speak | speak-no-evil monkey</annotation> + <annotation cp="🙊" type="tts">speak-no-evil monkey</annotation> + <annotation cp="💋">kiss | kiss mark | lips</annotation> + <annotation cp="💋" type="tts">kiss mark</annotation> + <annotation cp="💌">heart | letter | love | mail</annotation> + <annotation cp="💌" type="tts">love letter</annotation> + <annotation cp="💘">arrow | cupid | heart with arrow</annotation> + <annotation cp="💘" type="tts">heart with arrow</annotation> + <annotation cp="💝">heart with ribbon | ribbon | valentine</annotation> + <annotation cp="💝" type="tts">heart with ribbon</annotation> + <annotation cp="💖">excited | sparkle | sparkling heart</annotation> + <annotation cp="💖" type="tts">sparkling heart</annotation> + <annotation cp="💗">excited | growing | growing heart | nervous | pulse</annotation> + <annotation cp="💗" type="tts">growing heart</annotation> + <annotation cp="💓">beating | beating heart | heartbeat | pulsating</annotation> + <annotation cp="💓" type="tts">beating heart</annotation> + <annotation cp="💞">revolving | revolving hearts</annotation> + <annotation cp="💞" type="tts">revolving hearts</annotation> + <annotation cp="💕">love | two hearts</annotation> + <annotation cp="💕" type="tts">two hearts</annotation> + <annotation cp="💟">heart | heart decoration</annotation> + <annotation cp="💟" type="tts">heart decoration</annotation> + <annotation cp="❣">exclamation | heart exclamation | mark | punctuation</annotation> + <annotation cp="❣" type="tts">heart exclamation</annotation> + <annotation cp="💔">break | broken | broken heart</annotation> + <annotation cp="💔" type="tts">broken heart</annotation> + <annotation cp="❤🔥">burn | heart | heart on fire | love | lust | sacred heart</annotation> <!-- 2764 200D 1F525 --> + <annotation cp="❤🔥" type="tts">heart on fire</annotation> + <annotation cp="❤🩹">healthier | improving | mending | mending heart | recovering | recuperating | well</annotation> <!-- 2764 200D 1FA79 --> + <annotation cp="❤🩹" type="tts">mending heart</annotation> + <annotation cp="❤">heart | red heart</annotation> + <annotation cp="❤" type="tts">red heart</annotation> + <annotation cp="🧡">orange | orange heart</annotation> + <annotation cp="🧡" type="tts">orange heart</annotation> + <annotation cp="💛">yellow | yellow heart</annotation> + <annotation cp="💛" type="tts">yellow heart</annotation> + <annotation cp="💚">green | green heart</annotation> + <annotation cp="💚" type="tts">green heart</annotation> + <annotation cp="💙">blue | blue heart</annotation> + <annotation cp="💙" type="tts">blue heart</annotation> + <annotation cp="💜">purple | purple heart</annotation> + <annotation cp="💜" type="tts">purple heart</annotation> + <annotation cp="🤎">brown | heart</annotation> + <annotation cp="🤎" type="tts">brown heart</annotation> + <annotation cp="🖤">black | black heart | evil | wicked</annotation> + <annotation cp="🖤" type="tts">black heart</annotation> + <annotation cp="🤍">heart | white</annotation> + <annotation cp="🤍" type="tts">white heart</annotation> + <annotation cp="💯">100 | full | hundred | hundred points | score</annotation> + <annotation cp="💯" type="tts">hundred points</annotation> + <annotation cp="💢">anger symbol | angry | comic | mad</annotation> + <annotation cp="💢" type="tts">anger symbol</annotation> + <annotation cp="💥">boom | collision | comic</annotation> + <annotation cp="💥" type="tts">collision</annotation> + <annotation cp="💫">comic | dizzy | star</annotation> + <annotation cp="💫" type="tts">dizzy</annotation> + <annotation cp="💦">comic | splashing | sweat | sweat droplets</annotation> + <annotation cp="💦" type="tts">sweat droplets</annotation> + <annotation cp="💨">comic | dash | dashing away | running</annotation> + <annotation cp="💨" type="tts">dashing away</annotation> + <annotation cp="🕳">hole</annotation> + <annotation cp="🕳" type="tts">hole</annotation> + <annotation cp="💣">bomb | comic</annotation> + <annotation cp="💣" type="tts">bomb</annotation> + <annotation cp="💬">balloon | bubble | comic | dialog | speech</annotation> + <annotation cp="💬" type="tts">speech balloon</annotation> + <annotation cp="👁🗨">eye | eye in speech bubble | speech bubble | witness</annotation> + <annotation cp="👁🗨" type="tts">eye in speech bubble</annotation> + <annotation cp="🗨">dialog | left speech bubble | speech</annotation> + <annotation cp="🗨" type="tts">left speech bubble</annotation> + <annotation cp="🗯">angry | balloon | bubble | mad | right anger bubble</annotation> + <annotation cp="🗯" type="tts">right anger bubble</annotation> + <annotation cp="💭">balloon | bubble | comic | thought</annotation> + <annotation cp="💭" type="tts">thought balloon</annotation> + <annotation cp="💤">comic | sleep | zzz</annotation> + <annotation cp="💤" type="tts">zzz</annotation> + <annotation cp="👋">hand | wave | waving</annotation> + <annotation cp="👋" type="tts">waving hand</annotation> + <annotation cp="🤚">backhand | raised | raised back of hand</annotation> + <annotation cp="🤚" type="tts">raised back of hand</annotation> + <annotation cp="🖐">finger | hand | hand with fingers splayed | splayed</annotation> + <annotation cp="🖐" type="tts">hand with fingers splayed</annotation> + <annotation cp="✋">hand | high 5 | high five | raised hand</annotation> + <annotation cp="✋" type="tts">raised hand</annotation> + <annotation cp="🖖">finger | hand | spock | vulcan | vulcan salute</annotation> + <annotation cp="🖖" type="tts">vulcan salute</annotation> + <annotation cp="👌">hand | OK</annotation> + <annotation cp="👌" type="tts">OK hand</annotation> + <annotation cp="🤌">fingers | hand gesture | interrogation | pinched | sarcastic</annotation> + <annotation cp="🤌" type="tts">pinched fingers</annotation> + <annotation cp="🤏">pinching hand | small amount</annotation> + <annotation cp="🤏" type="tts">pinching hand</annotation> + <annotation cp="✌">hand | v | victory</annotation> + <annotation cp="✌" type="tts">victory hand</annotation> + <annotation cp="🤞">cross | crossed fingers | finger | hand | luck</annotation> + <annotation cp="🤞" type="tts">crossed fingers</annotation> + <annotation cp="🤟">hand | ILY | love-you gesture</annotation> + <annotation cp="🤟" type="tts">love-you gesture</annotation> + <annotation cp="🤘">finger | hand | horns | rock-on | sign of the horns</annotation> + <annotation cp="🤘" type="tts">sign of the horns</annotation> + <annotation cp="🤙">call | call me hand | hand</annotation> + <annotation cp="🤙" type="tts">call me hand</annotation> + <annotation cp="👈">backhand | backhand index pointing left | finger | hand | index | point</annotation> + <annotation cp="👈" type="tts">backhand index pointing left</annotation> + <annotation cp="👉">backhand | backhand index pointing right | finger | hand | index | point</annotation> + <annotation cp="👉" type="tts">backhand index pointing right</annotation> + <annotation cp="👆">backhand | backhand index pointing up | finger | hand | point | up</annotation> + <annotation cp="👆" type="tts">backhand index pointing up</annotation> + <annotation cp="🖕">finger | hand | middle finger</annotation> + <annotation cp="🖕" type="tts">middle finger</annotation> + <annotation cp="👇">backhand | backhand index pointing down | down | finger | hand | point</annotation> + <annotation cp="👇" type="tts">backhand index pointing down</annotation> + <annotation cp="☝">finger | hand | index | index pointing up | point | up</annotation> + <annotation cp="☝" type="tts">index pointing up</annotation> + <annotation cp="👍">+1 | hand | thumb | thumbs up | up</annotation> + <annotation cp="👍" type="tts">thumbs up</annotation> + <annotation cp="👎">-1 | down | hand | thumb | thumbs down</annotation> + <annotation cp="👎" type="tts">thumbs down</annotation> + <annotation cp="✊">clenched | fist | hand | punch | raised fist</annotation> + <annotation cp="✊" type="tts">raised fist</annotation> + <annotation cp="👊">clenched | fist | hand | oncoming fist | punch</annotation> + <annotation cp="👊" type="tts">oncoming fist</annotation> + <annotation cp="🤛">fist | left-facing fist | leftwards</annotation> + <annotation cp="🤛" type="tts">left-facing fist</annotation> + <annotation cp="🤜">fist | right-facing fist | rightwards</annotation> + <annotation cp="🤜" type="tts">right-facing fist</annotation> + <annotation cp="👏">clap | clapping hands | hand</annotation> + <annotation cp="👏" type="tts">clapping hands</annotation> + <annotation cp="🙌">celebration | gesture | hand | hooray | raised | raising hands</annotation> + <annotation cp="🙌" type="tts">raising hands</annotation> + <annotation cp="👐">hand | open | open hands</annotation> + <annotation cp="👐" type="tts">open hands</annotation> + <annotation cp="🤲">palms up together | prayer</annotation> + <annotation cp="🤲" type="tts">palms up together</annotation> + <annotation cp="🤝">agreement | hand | handshake | meeting | shake</annotation> + <annotation cp="🤝" type="tts">handshake</annotation> + <annotation cp="🙏">ask | folded hands | hand | high 5 | high five | please | pray | thanks</annotation> + <annotation cp="🙏" type="tts">folded hands</annotation> + <annotation cp="✍">hand | write | writing hand</annotation> + <annotation cp="✍" type="tts">writing hand</annotation> + <annotation cp="💅">care | cosmetics | manicure | nail | polish</annotation> + <annotation cp="💅" type="tts">nail polish</annotation> + <annotation cp="🤳">camera | phone | selfie</annotation> + <annotation cp="🤳" type="tts">selfie</annotation> + <annotation cp="💪">biceps | comic | flex | flexed biceps | muscle</annotation> + <annotation cp="💪" type="tts">flexed biceps</annotation> + <annotation cp="🦾">accessibility | mechanical arm | prosthetic</annotation> + <annotation cp="🦾" type="tts">mechanical arm</annotation> + <annotation cp="🦿">accessibility | mechanical leg | prosthetic</annotation> + <annotation cp="🦿" type="tts">mechanical leg</annotation> + <annotation cp="🦵">kick | leg | limb</annotation> + <annotation cp="🦵" type="tts">leg</annotation> + <annotation cp="🦶">foot | kick | stomp</annotation> + <annotation cp="🦶" type="tts">foot</annotation> + <annotation cp="👂">body | ear</annotation> + <annotation cp="👂" type="tts">ear</annotation> + <annotation cp="🦻">accessibility | ear with hearing aid | hard of hearing</annotation> + <annotation cp="🦻" type="tts">ear with hearing aid</annotation> + <annotation cp="👃">body | nose</annotation> + <annotation cp="👃" type="tts">nose</annotation> + <annotation cp="🧠">brain | intelligent</annotation> + <annotation cp="🧠" type="tts">brain</annotation> + <annotation cp="🫀">anatomical | cardiology | heart | organ | pulse</annotation> + <annotation cp="🫀" type="tts">anatomical heart</annotation> + <annotation cp="🫁">breath | exhalation | inhalation | lungs | organ | respiration</annotation> + <annotation cp="🫁" type="tts">lungs</annotation> + <annotation cp="🦷">dentist | tooth</annotation> + <annotation cp="🦷" type="tts">tooth</annotation> + <annotation cp="🦴">bone | skeleton</annotation> + <annotation cp="🦴" type="tts">bone</annotation> + <annotation cp="👀">eye | eyes | face</annotation> + <annotation cp="👀" type="tts">eyes</annotation> + <annotation cp="👁">body | eye</annotation> + <annotation cp="👁" type="tts">eye</annotation> + <annotation cp="👅">body | tongue</annotation> + <annotation cp="👅" type="tts">tongue</annotation> + <annotation cp="👄">lips | mouth</annotation> + <annotation cp="👄" type="tts">mouth</annotation> + <annotation cp="👶">baby | young</annotation> + <annotation cp="👶" type="tts">baby</annotation> + <annotation cp="🧒">child | gender-neutral | unspecified gender | young</annotation> + <annotation cp="🧒" type="tts">child</annotation> + <annotation cp="👦">boy | young</annotation> + <annotation cp="👦" type="tts">boy</annotation> + <annotation cp="👧">girl | Virgo | young | zodiac</annotation> + <annotation cp="👧" type="tts">girl</annotation> + <annotation cp="🧑">adult | gender-neutral | person | unspecified gender</annotation> + <annotation cp="🧑" type="tts">person</annotation> + <annotation cp="👱">blond | blond-haired person | hair | person: blond hair</annotation> + <annotation cp="👱" type="tts">person: blond hair</annotation> + <annotation cp="👨">adult | man</annotation> + <annotation cp="👨" type="tts">man</annotation> + <annotation cp="🧔">beard | person | person: beard</annotation> + <annotation cp="🧔" type="tts">person: beard</annotation> + <annotation cp="🧔♂">beard | man | man: beard</annotation> + <annotation cp="🧔♂" type="tts">man: beard</annotation> + <annotation cp="👱♂">blond | blond-haired man | hair | man | man: blond hair</annotation> + <annotation cp="👱♂" type="tts">man: blond hair</annotation> + <annotation cp="👩">adult | woman</annotation> + <annotation cp="👩" type="tts">woman</annotation> + <annotation cp="🧔♀">beard | woman | woman: beard</annotation> + <annotation cp="🧔♀" type="tts">woman: beard</annotation> + <annotation cp="👱♀">blond-haired woman | blonde | hair | woman | woman: blond hair</annotation> + <annotation cp="👱♀" type="tts">woman: blond hair</annotation> + <annotation cp="🧓">adult | gender-neutral | old | older person | unspecified gender</annotation> + <annotation cp="🧓" type="tts">older person</annotation> + <annotation cp="👴">adult | man | old</annotation> + <annotation cp="👴" type="tts">old man</annotation> + <annotation cp="👵">adult | old | woman</annotation> + <annotation cp="👵" type="tts">old woman</annotation> + <annotation cp="🙍">frown | gesture | person frowning</annotation> + <annotation cp="🙍" type="tts">person frowning</annotation> + <annotation cp="🙍♂">frowning | gesture | man</annotation> + <annotation cp="🙍♂" type="tts">man frowning</annotation> + <annotation cp="🙍♀">frowning | gesture | woman</annotation> + <annotation cp="🙍♀" type="tts">woman frowning</annotation> + <annotation cp="🙎">gesture | person pouting | pouting</annotation> + <annotation cp="🙎" type="tts">person pouting</annotation> + <annotation cp="🙎♂">gesture | man | pouting</annotation> + <annotation cp="🙎♂" type="tts">man pouting</annotation> + <annotation cp="🙎♀">gesture | pouting | woman</annotation> + <annotation cp="🙎♀" type="tts">woman pouting</annotation> + <annotation cp="🙅">forbidden | gesture | hand | person gesturing NO | prohibited</annotation> + <annotation cp="🙅" type="tts">person gesturing NO</annotation> + <annotation cp="🙅♂">forbidden | gesture | hand | man | man gesturing NO | prohibited</annotation> + <annotation cp="🙅♂" type="tts">man gesturing NO</annotation> + <annotation cp="🙅♀">forbidden | gesture | hand | prohibited | woman | woman gesturing NO</annotation> + <annotation cp="🙅♀" type="tts">woman gesturing NO</annotation> + <annotation cp="🙆">gesture | hand | OK | person gesturing OK</annotation> + <annotation cp="🙆" type="tts">person gesturing OK</annotation> + <annotation cp="🙆♂">gesture | hand | man | man gesturing OK | OK</annotation> + <annotation cp="🙆♂" type="tts">man gesturing OK</annotation> + <annotation cp="🙆♀">gesture | hand | OK | woman | woman gesturing OK</annotation> + <annotation cp="🙆♀" type="tts">woman gesturing OK</annotation> + <annotation cp="💁">hand | help | information | person tipping hand | sassy | tipping</annotation> + <annotation cp="💁" type="tts">person tipping hand</annotation> + <annotation cp="💁♂">man | man tipping hand | sassy | tipping hand</annotation> + <annotation cp="💁♂" type="tts">man tipping hand</annotation> + <annotation cp="💁♀">sassy | tipping hand | woman | woman tipping hand</annotation> + <annotation cp="💁♀" type="tts">woman tipping hand</annotation> + <annotation cp="🙋">gesture | hand | happy | person raising hand | raised</annotation> + <annotation cp="🙋" type="tts">person raising hand</annotation> + <annotation cp="🙋♂">gesture | man | man raising hand | raising hand</annotation> + <annotation cp="🙋♂" type="tts">man raising hand</annotation> + <annotation cp="🙋♀">gesture | raising hand | woman | woman raising hand</annotation> + <annotation cp="🙋♀" type="tts">woman raising hand</annotation> + <annotation cp="🧏">accessibility | deaf | deaf person | ear | hear</annotation> + <annotation cp="🧏" type="tts">deaf person</annotation> + <annotation cp="🧏♂">deaf | man</annotation> + <annotation cp="🧏♂" type="tts">deaf man</annotation> + <annotation cp="🧏♀">deaf | woman</annotation> + <annotation cp="🧏♀" type="tts">deaf woman</annotation> + <annotation cp="🙇">apology | bow | gesture | person bowing | sorry</annotation> + <annotation cp="🙇" type="tts">person bowing</annotation> + <annotation cp="🙇♂">apology | bowing | favor | gesture | man | sorry</annotation> + <annotation cp="🙇♂" type="tts">man bowing</annotation> + <annotation cp="🙇♀">apology | bowing | favor | gesture | sorry | woman</annotation> + <annotation cp="🙇♀" type="tts">woman bowing</annotation> + <annotation cp="🤦">disbelief | exasperation | face | palm | person facepalming</annotation> + <annotation cp="🤦" type="tts">person facepalming</annotation> + <annotation cp="🤦♂">disbelief | exasperation | facepalm | man | man facepalming</annotation> + <annotation cp="🤦♂" type="tts">man facepalming</annotation> + <annotation cp="🤦♀">disbelief | exasperation | facepalm | woman | woman facepalming</annotation> + <annotation cp="🤦♀" type="tts">woman facepalming</annotation> + <annotation cp="🤷">doubt | ignorance | indifference | person shrugging | shrug</annotation> + <annotation cp="🤷" type="tts">person shrugging</annotation> + <annotation cp="🤷♂">doubt | ignorance | indifference | man | man shrugging | shrug</annotation> + <annotation cp="🤷♂" type="tts">man shrugging</annotation> + <annotation cp="🤷♀">doubt | ignorance | indifference | shrug | woman | woman shrugging</annotation> + <annotation cp="🤷♀" type="tts">woman shrugging</annotation> + <annotation cp="🧑⚕">doctor | health worker | healthcare | nurse | therapist</annotation> + <annotation cp="🧑⚕" type="tts">health worker</annotation> + <annotation cp="👨⚕">doctor | healthcare | man | man health worker | nurse | therapist</annotation> + <annotation cp="👨⚕" type="tts">man health worker</annotation> + <annotation cp="👩⚕">doctor | healthcare | nurse | therapist | woman | woman health worker</annotation> + <annotation cp="👩⚕" type="tts">woman health worker</annotation> + <annotation cp="🧑🎓">graduate | student</annotation> + <annotation cp="🧑🎓" type="tts">student</annotation> + <annotation cp="👨🎓">graduate | man | student</annotation> + <annotation cp="👨🎓" type="tts">man student</annotation> + <annotation cp="👩🎓">graduate | student | woman</annotation> + <annotation cp="👩🎓" type="tts">woman student</annotation> + <annotation cp="🧑🏫">instructor | professor | teacher</annotation> + <annotation cp="🧑🏫" type="tts">teacher</annotation> + <annotation cp="👨🏫">instructor | man | professor | teacher</annotation> + <annotation cp="👨🏫" type="tts">man teacher</annotation> + <annotation cp="👩🏫">instructor | professor | teacher | woman</annotation> + <annotation cp="👩🏫" type="tts">woman teacher</annotation> + <annotation cp="🧑⚖">judge | justice | scales</annotation> + <annotation cp="🧑⚖" type="tts">judge</annotation> + <annotation cp="👨⚖">judge | justice | man | scales</annotation> + <annotation cp="👨⚖" type="tts">man judge</annotation> + <annotation cp="👩⚖">judge | justice | scales | woman</annotation> + <annotation cp="👩⚖" type="tts">woman judge</annotation> + <annotation cp="🧑🌾">farmer | gardener | rancher</annotation> + <annotation cp="🧑🌾" type="tts">farmer</annotation> + <annotation cp="👨🌾">farmer | gardener | man | rancher</annotation> + <annotation cp="👨🌾" type="tts">man farmer</annotation> + <annotation cp="👩🌾">farmer | gardener | rancher | woman</annotation> + <annotation cp="👩🌾" type="tts">woman farmer</annotation> + <annotation cp="🧑🍳">chef | cook</annotation> + <annotation cp="🧑🍳" type="tts">cook</annotation> + <annotation cp="👨🍳">chef | cook | man</annotation> + <annotation cp="👨🍳" type="tts">man cook</annotation> + <annotation cp="👩🍳">chef | cook | woman</annotation> + <annotation cp="👩🍳" type="tts">woman cook</annotation> + <annotation cp="🧑🔧">electrician | mechanic | plumber | tradesperson</annotation> + <annotation cp="🧑🔧" type="tts">mechanic</annotation> + <annotation cp="👨🔧">electrician | man | mechanic | plumber | tradesperson</annotation> + <annotation cp="👨🔧" type="tts">man mechanic</annotation> + <annotation cp="👩🔧">electrician | mechanic | plumber | tradesperson | woman</annotation> + <annotation cp="👩🔧" type="tts">woman mechanic</annotation> + <annotation cp="🧑🏭">assembly | factory | industrial | worker</annotation> + <annotation cp="🧑🏭" type="tts">factory worker</annotation> + <annotation cp="👨🏭">assembly | factory | industrial | man | worker</annotation> + <annotation cp="👨🏭" type="tts">man factory worker</annotation> + <annotation cp="👩🏭">assembly | factory | industrial | woman | worker</annotation> + <annotation cp="👩🏭" type="tts">woman factory worker</annotation> + <annotation cp="🧑💼">architect | business | manager | office worker | white-collar</annotation> + <annotation cp="🧑💼" type="tts">office worker</annotation> + <annotation cp="👨💼">architect | business | man | man office worker | manager | white-collar</annotation> + <annotation cp="👨💼" type="tts">man office worker</annotation> + <annotation cp="👩💼">architect | business | manager | white-collar | woman | woman office worker</annotation> + <annotation cp="👩💼" type="tts">woman office worker</annotation> + <annotation cp="🧑🔬">biologist | chemist | engineer | physicist | scientist</annotation> + <annotation cp="🧑🔬" type="tts">scientist</annotation> + <annotation cp="👨🔬">biologist | chemist | engineer | man | physicist | scientist</annotation> + <annotation cp="👨🔬" type="tts">man scientist</annotation> + <annotation cp="👩🔬">biologist | chemist | engineer | physicist | scientist | woman</annotation> + <annotation cp="👩🔬" type="tts">woman scientist</annotation> + <annotation cp="🧑💻">coder | developer | inventor | software | technologist</annotation> + <annotation cp="🧑💻" type="tts">technologist</annotation> + <annotation cp="👨💻">coder | developer | inventor | man | software | technologist</annotation> + <annotation cp="👨💻" type="tts">man technologist</annotation> + <annotation cp="👩💻">coder | developer | inventor | software | technologist | woman</annotation> + <annotation cp="👩💻" type="tts">woman technologist</annotation> + <annotation cp="🧑🎤">actor | entertainer | rock | singer | star</annotation> + <annotation cp="🧑🎤" type="tts">singer</annotation> + <annotation cp="👨🎤">actor | entertainer | man | rock | singer | star</annotation> + <annotation cp="👨🎤" type="tts">man singer</annotation> + <annotation cp="👩🎤">actor | entertainer | rock | singer | star | woman</annotation> + <annotation cp="👩🎤" type="tts">woman singer</annotation> + <annotation cp="🧑🎨">artist | palette</annotation> + <annotation cp="🧑🎨" type="tts">artist</annotation> + <annotation cp="👨🎨">artist | man | palette</annotation> + <annotation cp="👨🎨" type="tts">man artist</annotation> + <annotation cp="👩🎨">artist | palette | woman</annotation> + <annotation cp="👩🎨" type="tts">woman artist</annotation> + <annotation cp="🧑✈">pilot | plane</annotation> + <annotation cp="🧑✈" type="tts">pilot</annotation> + <annotation cp="👨✈">man | pilot | plane</annotation> + <annotation cp="👨✈" type="tts">man pilot</annotation> + <annotation cp="👩✈">pilot | plane | woman</annotation> + <annotation cp="👩✈" type="tts">woman pilot</annotation> + <annotation cp="🧑🚀">astronaut | rocket</annotation> + <annotation cp="🧑🚀" type="tts">astronaut</annotation> + <annotation cp="👨🚀">astronaut | man | rocket</annotation> + <annotation cp="👨🚀" type="tts">man astronaut</annotation> + <annotation cp="👩🚀">astronaut | rocket | woman</annotation> + <annotation cp="👩🚀" type="tts">woman astronaut</annotation> + <annotation cp="🧑🚒">firefighter | firetruck</annotation> + <annotation cp="🧑🚒" type="tts">firefighter</annotation> + <annotation cp="👨🚒">firefighter | firetruck | man</annotation> + <annotation cp="👨🚒" type="tts">man firefighter</annotation> + <annotation cp="👩🚒">firefighter | firetruck | woman</annotation> + <annotation cp="👩🚒" type="tts">woman firefighter</annotation> + <annotation cp="👮">cop | officer | police</annotation> + <annotation cp="👮" type="tts">police officer</annotation> + <annotation cp="👮♂">cop | man | officer | police</annotation> + <annotation cp="👮♂" type="tts">man police officer</annotation> + <annotation cp="👮♀">cop | officer | police | woman</annotation> + <annotation cp="👮♀" type="tts">woman police officer</annotation> + <annotation cp="🕵">detective | sleuth | spy</annotation> + <annotation cp="🕵" type="tts">detective</annotation> + <annotation cp="🕵♂">detective | man | sleuth | spy</annotation> + <annotation cp="🕵♂" type="tts">man detective</annotation> + <annotation cp="🕵♀">detective | sleuth | spy | woman</annotation> + <annotation cp="🕵♀" type="tts">woman detective</annotation> + <annotation cp="💂">guard</annotation> + <annotation cp="💂" type="tts">guard</annotation> + <annotation cp="💂♂">guard | man</annotation> + <annotation cp="💂♂" type="tts">man guard</annotation> + <annotation cp="💂♀">guard | woman</annotation> + <annotation cp="💂♀" type="tts">woman guard</annotation> + <annotation cp="🥷">fighter | hidden | ninja | stealth</annotation> + <annotation cp="🥷" type="tts">ninja</annotation> + <annotation cp="👷">construction | hat | worker</annotation> + <annotation cp="👷" type="tts">construction worker</annotation> + <annotation cp="👷♂">construction | man | worker</annotation> + <annotation cp="👷♂" type="tts">man construction worker</annotation> + <annotation cp="👷♀">construction | woman | worker</annotation> + <annotation cp="👷♀" type="tts">woman construction worker</annotation> + <annotation cp="🤴">prince</annotation> + <annotation cp="🤴" type="tts">prince</annotation> + <annotation cp="👸">fairy tale | fantasy | princess</annotation> + <annotation cp="👸" type="tts">princess</annotation> + <annotation cp="👳">person wearing turban | turban</annotation> + <annotation cp="👳" type="tts">person wearing turban</annotation> + <annotation cp="👳♂">man | man wearing turban | turban</annotation> + <annotation cp="👳♂" type="tts">man wearing turban</annotation> + <annotation cp="👳♀">turban | woman | woman wearing turban</annotation> + <annotation cp="👳♀" type="tts">woman wearing turban</annotation> + <annotation cp="👲">cap | gua pi mao | hat | person | person with skullcap | skullcap</annotation> + <annotation cp="👲" type="tts">person with skullcap</annotation> + <annotation cp="🧕">headscarf | hijab | mantilla | tichel | woman with headscarf</annotation> + <annotation cp="🧕" type="tts">woman with headscarf</annotation> + <annotation cp="🤵">groom | person | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵" type="tts">person in tuxedo</annotation> + <!-- (more) Generated lines from Emoji Data v13, using GenerateCldrData.java --> + <annotation cp="🤵♂">man | man in tuxedo | tuxedo</annotation> <!-- 1F935 200D 2642 --> + <annotation cp="🤵♂" type="tts">man in tuxedo</annotation> + <annotation cp="🤵♀">tuxedo | woman | woman in tuxedo</annotation> <!-- 1F935 200D 2640 --> + <annotation cp="🤵♀" type="tts">woman in tuxedo</annotation> + <annotation cp="👰">bride | person | person with veil | veil | wedding</annotation> + <annotation cp="👰" type="tts">person with veil</annotation> + <annotation cp="👰♂">man | man with veil | veil</annotation> <!-- 1F470 200D 2642 --> + <annotation cp="👰♂" type="tts">man with veil</annotation> + <annotation cp="👰♀">veil | woman | woman with veil</annotation> <!-- 1F470 200D 2640 --> + <annotation cp="👰♀" type="tts">woman with veil</annotation> + <annotation cp="🤰">pregnant | woman</annotation> + <annotation cp="🤰" type="tts">pregnant woman</annotation> + <annotation cp="🤱">baby | breast | breast-feeding | nursing</annotation> + <annotation cp="🤱" type="tts">breast-feeding</annotation> + <annotation cp="👩🍼">baby | feeding | nursing | woman</annotation> <!-- 1F469 200D 1F37C --> + <annotation cp="👩🍼" type="tts">woman feeding baby</annotation> + <annotation cp="👨🍼">baby | feeding | man | nursing</annotation> <!-- 1F468 200D 1F37C --> + <annotation cp="👨🍼" type="tts">man feeding baby</annotation> + <annotation cp="🧑🍼">baby | feeding | nursing | person</annotation> <!-- 1F9D1 200D 1F37C --> + <annotation cp="🧑🍼" type="tts">person feeding baby</annotation> + <annotation cp="👼">angel | baby | face | fairy tale | fantasy</annotation> + <annotation cp="👼" type="tts">baby angel</annotation> + <annotation cp="🎅">celebration | Christmas | claus | father | santa | Santa Claus</annotation> + <annotation cp="🎅" type="tts">Santa Claus</annotation> + <annotation cp="🤶">celebration | Christmas | claus | mother | Mrs. | Mrs. Claus</annotation> + <annotation cp="🤶" type="tts">Mrs. Claus</annotation> + <annotation cp="🧑🎄">Claus, christmas | mx claus</annotation> <!-- 1F9D1 200D 1F384 --> + <annotation cp="🧑🎄" type="tts">mx claus</annotation> + <annotation cp="🦸">good | hero | heroine | superhero | superpower</annotation> + <annotation cp="🦸" type="tts">superhero</annotation> + <annotation cp="🦸♂">good | hero | man | man superhero | superpower</annotation> + <annotation cp="🦸♂" type="tts">man superhero</annotation> + <annotation cp="🦸♀">good | hero | heroine | superpower | woman | woman superhero</annotation> + <annotation cp="🦸♀" type="tts">woman superhero</annotation> + <annotation cp="🦹">criminal | evil | superpower | supervillain | villain</annotation> + <annotation cp="🦹" type="tts">supervillain</annotation> + <annotation cp="🦹♂">criminal | evil | man | man supervillain | superpower | villain</annotation> + <annotation cp="🦹♂" type="tts">man supervillain</annotation> + <annotation cp="🦹♀">criminal | evil | superpower | villain | woman | woman supervillain</annotation> + <annotation cp="🦹♀" type="tts">woman supervillain</annotation> + <annotation cp="🧙">mage | sorcerer | sorceress | witch | wizard</annotation> + <annotation cp="🧙" type="tts">mage</annotation> + <annotation cp="🧙♂">man mage | sorcerer | wizard</annotation> + <annotation cp="🧙♂" type="tts">man mage</annotation> + <annotation cp="🧙♀">sorceress | witch | woman mage</annotation> + <annotation cp="🧙♀" type="tts">woman mage</annotation> + <annotation cp="🧚">fairy | Oberon | Puck | Titania</annotation> + <annotation cp="🧚" type="tts">fairy</annotation> + <annotation cp="🧚♂">man fairy | Oberon | Puck</annotation> + <annotation cp="🧚♂" type="tts">man fairy</annotation> + <annotation cp="🧚♀">Titania | woman fairy</annotation> + <annotation cp="🧚♀" type="tts">woman fairy</annotation> + <annotation cp="🧛">Dracula | undead | vampire</annotation> + <annotation cp="🧛" type="tts">vampire</annotation> + <annotation cp="🧛♂">Dracula | man vampire | undead</annotation> + <annotation cp="🧛♂" type="tts">man vampire</annotation> + <annotation cp="🧛♀">undead | woman vampire</annotation> + <annotation cp="🧛♀" type="tts">woman vampire</annotation> + <annotation cp="🧜">mermaid | merman | merperson | merwoman</annotation> + <annotation cp="🧜" type="tts">merperson</annotation> + <annotation cp="🧜♂">merman | Triton</annotation> + <annotation cp="🧜♂" type="tts">merman</annotation> + <annotation cp="🧜♀">mermaid | merwoman</annotation> + <annotation cp="🧜♀" type="tts">mermaid</annotation> + <annotation cp="🧝">elf | magical</annotation> + <annotation cp="🧝" type="tts">elf</annotation> + <annotation cp="🧝♂">magical | man elf</annotation> + <annotation cp="🧝♂" type="tts">man elf</annotation> + <annotation cp="🧝♀">magical | woman elf</annotation> + <annotation cp="🧝♀" type="tts">woman elf</annotation> + <annotation cp="🧞">djinn | genie</annotation> + <annotation cp="🧞" type="tts">genie</annotation> + <annotation cp="🧞♂">djinn | man genie</annotation> + <annotation cp="🧞♂" type="tts">man genie</annotation> + <annotation cp="🧞♀">djinn | woman genie</annotation> + <annotation cp="🧞♀" type="tts">woman genie</annotation> + <annotation cp="🧟">undead | walking dead | zombie</annotation> + <annotation cp="🧟" type="tts">zombie</annotation> + <annotation cp="🧟♂">man zombie | undead | walking dead</annotation> + <annotation cp="🧟♂" type="tts">man zombie</annotation> + <annotation cp="🧟♀">undead | walking dead | woman zombie</annotation> + <annotation cp="🧟♀" type="tts">woman zombie</annotation> + <annotation cp="💆">face | massage | person getting massage | salon</annotation> + <annotation cp="💆" type="tts">person getting massage</annotation> + <annotation cp="💆♂">face | man | man getting massage | massage</annotation> + <annotation cp="💆♂" type="tts">man getting massage</annotation> + <annotation cp="💆♀">face | massage | woman | woman getting massage</annotation> + <annotation cp="💆♀" type="tts">woman getting massage</annotation> + <annotation cp="💇">barber | beauty | haircut | parlor | person getting haircut</annotation> + <annotation cp="💇" type="tts">person getting haircut</annotation> + <annotation cp="💇♂">haircut | man | man getting haircut</annotation> + <annotation cp="💇♂" type="tts">man getting haircut</annotation> + <annotation cp="💇♀">haircut | woman | woman getting haircut</annotation> + <annotation cp="💇♀" type="tts">woman getting haircut</annotation> + <annotation cp="🚶">hike | person walking | walk | walking</annotation> + <annotation cp="🚶" type="tts">person walking</annotation> + <annotation cp="🚶♂">hike | man | man walking | walk</annotation> + <annotation cp="🚶♂" type="tts">man walking</annotation> + <annotation cp="🚶♀">hike | walk | woman | woman walking</annotation> + <annotation cp="🚶♀" type="tts">woman walking</annotation> + <annotation cp="🧍">person standing | stand | standing</annotation> + <annotation cp="🧍" type="tts">person standing</annotation> + <annotation cp="🧍♂">man | standing</annotation> + <annotation cp="🧍♂" type="tts">man standing</annotation> + <annotation cp="🧍♀">standing | woman</annotation> + <annotation cp="🧍♀" type="tts">woman standing</annotation> + <annotation cp="🧎">kneel | kneeling | person kneeling</annotation> + <annotation cp="🧎" type="tts">person kneeling</annotation> + <annotation cp="🧎♂">kneeling | man</annotation> + <annotation cp="🧎♂" type="tts">man kneeling</annotation> + <annotation cp="🧎♀">kneeling | woman</annotation> + <annotation cp="🧎♀" type="tts">woman kneeling</annotation> + <annotation cp="🧑🦯">accessibility | blind | person with white cane</annotation> + <annotation cp="🧑🦯" type="tts">person with white cane</annotation> + <annotation cp="👨🦯">accessibility | blind | man | man with white cane</annotation> + <annotation cp="👨🦯" type="tts">man with white cane</annotation> + <annotation cp="👩🦯">accessibility | blind | woman | woman with white cane</annotation> + <annotation cp="👩🦯" type="tts">woman with white cane</annotation> + <annotation cp="🧑🦼">accessibility | person in motorized wheelchair | wheelchair</annotation> + <annotation cp="🧑🦼" type="tts">person in motorized wheelchair</annotation> + <annotation cp="👨🦼">accessibility | man | man in motorized wheelchair | wheelchair</annotation> + <annotation cp="👨🦼" type="tts">man in motorized wheelchair</annotation> + <annotation cp="👩🦼">accessibility | wheelchair | woman | woman in motorized wheelchair</annotation> + <annotation cp="👩🦼" type="tts">woman in motorized wheelchair</annotation> + <annotation cp="🧑🦽">accessibility | person in manual wheelchair | wheelchair</annotation> + <annotation cp="🧑🦽" type="tts">person in manual wheelchair</annotation> + <annotation cp="👨🦽">accessibility | man | man in manual wheelchair | wheelchair</annotation> + <annotation cp="👨🦽" type="tts">man in manual wheelchair</annotation> + <annotation cp="👩🦽">accessibility | wheelchair | woman | woman in manual wheelchair</annotation> + <annotation cp="👩🦽" type="tts">woman in manual wheelchair</annotation> + <annotation cp="🏃">marathon | person running | running</annotation> + <annotation cp="🏃" type="tts">person running</annotation> + <annotation cp="🏃♂">man | marathon | racing | running</annotation> + <annotation cp="🏃♂" type="tts">man running</annotation> + <annotation cp="🏃♀">marathon | racing | running | woman</annotation> + <annotation cp="🏃♀" type="tts">woman running</annotation> + <annotation cp="💃">dance | dancing | woman</annotation> + <annotation cp="💃" type="tts">woman dancing</annotation> + <annotation cp="🕺">dance | dancing | man</annotation> + <annotation cp="🕺" type="tts">man dancing</annotation> + <annotation cp="🕴">business | person | person in suit levitating | suit</annotation> + <annotation cp="🕴" type="tts">person in suit levitating</annotation> + <annotation cp="👯">bunny ear | dancer | partying | people with bunny ears</annotation> + <annotation cp="👯" type="tts">people with bunny ears</annotation> + <annotation cp="👯♂">bunny ear | dancer | men | men with bunny ears | partying</annotation> + <annotation cp="👯♂" type="tts">men with bunny ears</annotation> + <annotation cp="👯♀">bunny ear | dancer | partying | women | women with bunny ears</annotation> + <annotation cp="👯♀" type="tts">women with bunny ears</annotation> + <annotation cp="🧖">person in steamy room | sauna | steam room</annotation> + <annotation cp="🧖" type="tts">person in steamy room</annotation> + <annotation cp="🧖♂">man in steamy room | sauna | steam room</annotation> + <annotation cp="🧖♂" type="tts">man in steamy room</annotation> + <annotation cp="🧖♀">sauna | steam room | woman in steamy room</annotation> + <annotation cp="🧖♀" type="tts">woman in steamy room</annotation> + <annotation cp="🧗">climber | person climbing</annotation> + <annotation cp="🧗" type="tts">person climbing</annotation> + <annotation cp="🧗♂">climber | man climbing</annotation> + <annotation cp="🧗♂" type="tts">man climbing</annotation> + <annotation cp="🧗♀">climber | woman climbing</annotation> + <annotation cp="🧗♀" type="tts">woman climbing</annotation> + <annotation cp="🤺">fencer | fencing | person fencing | sword</annotation> + <annotation cp="🤺" type="tts">person fencing</annotation> + <annotation cp="🏇">horse | jockey | racehorse | racing</annotation> + <annotation cp="🏇" type="tts">horse racing</annotation> + <annotation cp="⛷">ski | skier | snow</annotation> + <annotation cp="⛷" type="tts">skier</annotation> + <annotation cp="🏂">ski | snow | snowboard | snowboarder</annotation> + <annotation cp="🏂" type="tts">snowboarder</annotation> + <annotation cp="🏌">ball | golf | person golfing</annotation> + <annotation cp="🏌" type="tts">person golfing</annotation> + <annotation cp="🏌♂">golf | man | man golfing</annotation> + <annotation cp="🏌♂" type="tts">man golfing</annotation> + <annotation cp="🏌♀">golf | woman | woman golfing</annotation> + <annotation cp="🏌♀" type="tts">woman golfing</annotation> + <annotation cp="🏄">person surfing | surfing</annotation> + <annotation cp="🏄" type="tts">person surfing</annotation> + <annotation cp="🏄♂">man | surfing</annotation> + <annotation cp="🏄♂" type="tts">man surfing</annotation> + <annotation cp="🏄♀">surfing | woman</annotation> + <annotation cp="🏄♀" type="tts">woman surfing</annotation> + <annotation cp="🚣">boat | person rowing boat | rowboat</annotation> + <annotation cp="🚣" type="tts">person rowing boat</annotation> + <annotation cp="🚣♂">boat | man | man rowing boat | rowboat</annotation> + <annotation cp="🚣♂" type="tts">man rowing boat</annotation> + <annotation cp="🚣♀">boat | rowboat | woman | woman rowing boat</annotation> + <annotation cp="🚣♀" type="tts">woman rowing boat</annotation> + <annotation cp="🏊">person swimming | swim</annotation> + <annotation cp="🏊" type="tts">person swimming</annotation> + <annotation cp="🏊♂">man | man swimming | swim</annotation> + <annotation cp="🏊♂" type="tts">man swimming</annotation> + <annotation cp="🏊♀">swim | woman | woman swimming</annotation> + <annotation cp="🏊♀" type="tts">woman swimming</annotation> + <annotation cp="⛹">ball | person bouncing ball</annotation> + <annotation cp="⛹" type="tts">person bouncing ball</annotation> + <annotation cp="⛹♂">ball | man | man bouncing ball</annotation> + <annotation cp="⛹♂" type="tts">man bouncing ball</annotation> + <annotation cp="⛹♀">ball | woman | woman bouncing ball</annotation> + <annotation cp="⛹♀" type="tts">woman bouncing ball</annotation> + <annotation cp="🏋">lifter | person lifting weights | weight</annotation> + <annotation cp="🏋" type="tts">person lifting weights</annotation> + <annotation cp="🏋♂">man | man lifting weights | weight lifter</annotation> + <annotation cp="🏋♂" type="tts">man lifting weights</annotation> + <annotation cp="🏋♀">weight lifter | woman | woman lifting weights</annotation> + <annotation cp="🏋♀" type="tts">woman lifting weights</annotation> + <annotation cp="🚴">bicycle | biking | cyclist | person biking</annotation> + <annotation cp="🚴" type="tts">person biking</annotation> + <annotation cp="🚴♂">bicycle | biking | cyclist | man</annotation> + <annotation cp="🚴♂" type="tts">man biking</annotation> + <annotation cp="🚴♀">bicycle | biking | cyclist | woman</annotation> + <annotation cp="🚴♀" type="tts">woman biking</annotation> + <annotation cp="🚵">bicycle | bicyclist | bike | cyclist | mountain | person mountain biking</annotation> + <annotation cp="🚵" type="tts">person mountain biking</annotation> + <annotation cp="🚵♂">bicycle | bike | cyclist | man | man mountain biking | mountain</annotation> + <annotation cp="🚵♂" type="tts">man mountain biking</annotation> + <annotation cp="🚵♀">bicycle | bike | biking | cyclist | mountain | woman</annotation> + <annotation cp="🚵♀" type="tts">woman mountain biking</annotation> + <annotation cp="🤸">cartwheel | gymnastics | person cartwheeling</annotation> + <annotation cp="🤸" type="tts">person cartwheeling</annotation> + <annotation cp="🤸♂">cartwheel | gymnastics | man | man cartwheeling</annotation> + <annotation cp="🤸♂" type="tts">man cartwheeling</annotation> + <annotation cp="🤸♀">cartwheel | gymnastics | woman | woman cartwheeling</annotation> + <annotation cp="🤸♀" type="tts">woman cartwheeling</annotation> + <annotation cp="🤼">people wrestling | wrestle | wrestler</annotation> + <annotation cp="🤼" type="tts">people wrestling</annotation> + <annotation cp="🤼♂">men | men wrestling | wrestle</annotation> + <annotation cp="🤼♂" type="tts">men wrestling</annotation> + <annotation cp="🤼♀">women | women wrestling | wrestle</annotation> + <annotation cp="🤼♀" type="tts">women wrestling</annotation> + <annotation cp="🤽">person playing water polo | polo | water</annotation> + <annotation cp="🤽" type="tts">person playing water polo</annotation> + <annotation cp="🤽♂">man | man playing water polo | water polo</annotation> + <annotation cp="🤽♂" type="tts">man playing water polo</annotation> + <annotation cp="🤽♀">water polo | woman | woman playing water polo</annotation> + <annotation cp="🤽♀" type="tts">woman playing water polo</annotation> + <annotation cp="🤾">ball | handball | person playing handball</annotation> + <annotation cp="🤾" type="tts">person playing handball</annotation> + <annotation cp="🤾♂">handball | man | man playing handball</annotation> + <annotation cp="🤾♂" type="tts">man playing handball</annotation> + <annotation cp="🤾♀">handball | woman | woman playing handball</annotation> + <annotation cp="🤾♀" type="tts">woman playing handball</annotation> + <annotation cp="🤹">balance | juggle | multitask | person juggling | skill</annotation> + <annotation cp="🤹" type="tts">person juggling</annotation> + <annotation cp="🤹♂">juggling | man | multitask</annotation> + <annotation cp="🤹♂" type="tts">man juggling</annotation> + <annotation cp="🤹♀">juggling | multitask | woman</annotation> + <annotation cp="🤹♀" type="tts">woman juggling</annotation> + <annotation cp="🧘">meditation | person in lotus position | yoga</annotation> + <annotation cp="🧘" type="tts">person in lotus position</annotation> + <annotation cp="🧘♂">man in lotus position | meditation | yoga</annotation> + <annotation cp="🧘♂" type="tts">man in lotus position</annotation> + <annotation cp="🧘♀">meditation | woman in lotus position | yoga</annotation> + <annotation cp="🧘♀" type="tts">woman in lotus position</annotation> + <annotation cp="🛀">bath | bathtub | person taking bath</annotation> + <annotation cp="🛀" type="tts">person taking bath</annotation> + <annotation cp="🛌">hotel | person in bed | sleep</annotation> + <annotation cp="🛌" type="tts">person in bed</annotation> + <annotation cp="🧑🤝🧑">couple | hand | hold | holding hands | people holding hands | person</annotation> + <annotation cp="🧑🤝🧑" type="tts">people holding hands</annotation> + <annotation cp="👭">couple | hand | holding hands | women | women holding hands</annotation> + <annotation cp="👭" type="tts">women holding hands</annotation> + <annotation cp="👫">couple | hand | hold | holding hands | man | woman | woman and man holding hands</annotation> + <annotation cp="👫" type="tts">woman and man holding hands</annotation> + <annotation cp="👬">couple | Gemini | holding hands | man | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👬" type="tts">men holding hands</annotation> + <annotation cp="💏">couple | kiss</annotation> + <annotation cp="💏" type="tts">kiss</annotation> + <annotation cp="💑">couple | couple with heart | love</annotation> + <annotation cp="💑" type="tts">couple with heart</annotation> + <annotation cp="👪">family</annotation> + <annotation cp="👪" type="tts">family</annotation> + <annotation cp="🗣">face | head | silhouette | speak | speaking</annotation> + <annotation cp="🗣" type="tts">speaking head</annotation> + <annotation cp="👤">bust | bust in silhouette | silhouette</annotation> + <annotation cp="👤" type="tts">bust in silhouette</annotation> + <annotation cp="👥">bust | busts in silhouette | silhouette</annotation> + <annotation cp="👥" type="tts">busts in silhouette</annotation> + <annotation cp="🫂">goodbye | hello | hug | people hugging | thanks</annotation> + <annotation cp="🫂" type="tts">people hugging</annotation> + <annotation cp="👣">clothing | footprint | footprints | print</annotation> + <annotation cp="👣" type="tts">footprints</annotation> + <annotation cp="🦰">ginger | red hair | redhead</annotation> + <annotation cp="🦰" type="tts">red hair</annotation> + <annotation cp="🦱">afro | curly | curly hair | ringlets</annotation> + <annotation cp="🦱" type="tts">curly hair</annotation> + <annotation cp="🦳">gray | hair | old | white</annotation> + <annotation cp="🦳" type="tts">white hair</annotation> + <annotation cp="🦲">bald | chemotherapy | hairless | no hair | shaven</annotation> + <annotation cp="🦲" type="tts">bald</annotation> + <annotation cp="🐵">face | monkey</annotation> + <annotation cp="🐵" type="tts">monkey face</annotation> + <annotation cp="🐒">monkey</annotation> + <annotation cp="🐒" type="tts">monkey</annotation> + <annotation cp="🦍">gorilla</annotation> + <annotation cp="🦍" type="tts">gorilla</annotation> + <annotation cp="🦧">ape | orangutan</annotation> + <annotation cp="🦧" type="tts">orangutan</annotation> + <annotation cp="🐶">dog | face | pet</annotation> + <annotation cp="🐶" type="tts">dog face</annotation> + <annotation cp="🐕">dog | pet</annotation> + <annotation cp="🐕" type="tts">dog</annotation> + <annotation cp="🦮">accessibility | blind | guide | guide dog</annotation> + <annotation cp="🦮" type="tts">guide dog</annotation> + <annotation cp="🐕🦺">accessibility | assistance | dog | service</annotation> + <annotation cp="🐕🦺" type="tts">service dog</annotation> + <annotation cp="🐩">dog | poodle</annotation> + <annotation cp="🐩" type="tts">poodle</annotation> + <annotation cp="🐺">face | wolf</annotation> + <annotation cp="🐺" type="tts">wolf</annotation> + <annotation cp="🦊">face | fox</annotation> + <annotation cp="🦊" type="tts">fox</annotation> + <annotation cp="🦝">curious | raccoon | sly</annotation> + <annotation cp="🦝" type="tts">raccoon</annotation> + <annotation cp="🐱">cat | face | pet</annotation> + <annotation cp="🐱" type="tts">cat face</annotation> + <annotation cp="🐈">cat | pet</annotation> + <annotation cp="🐈" type="tts">cat</annotation> + <annotation cp="🐈⬛">black | cat | unlucky</annotation> <!-- 1F408 200D 2B1B --> + <annotation cp="🐈⬛" type="tts">black cat</annotation> + <annotation cp="🦁">face | Leo | lion | zodiac</annotation> + <annotation cp="🦁" type="tts">lion</annotation> + <annotation cp="🐯">face | tiger</annotation> + <annotation cp="🐯" type="tts">tiger face</annotation> + <annotation cp="🐅">tiger</annotation> + <annotation cp="🐅" type="tts">tiger</annotation> + <annotation cp="🐆">leopard</annotation> + <annotation cp="🐆" type="tts">leopard</annotation> + <annotation cp="🐴">face | horse</annotation> + <annotation cp="🐴" type="tts">horse face</annotation> + <annotation cp="🐎">equestrian | horse | racehorse | racing</annotation> + <annotation cp="🐎" type="tts">horse</annotation> + <annotation cp="🦄">face | unicorn</annotation> + <annotation cp="🦄" type="tts">unicorn</annotation> + <annotation cp="🦓">stripe | zebra</annotation> + <annotation cp="🦓" type="tts">zebra</annotation> + <annotation cp="🦌">deer</annotation> + <annotation cp="🦌" type="tts">deer</annotation> + <annotation cp="🦬">bison | buffalo | herd | wisent</annotation> + <annotation cp="🦬" type="tts">bison</annotation> + <annotation cp="🐮">cow | face</annotation> + <annotation cp="🐮" type="tts">cow face</annotation> + <annotation cp="🐂">bull | ox | Taurus | zodiac</annotation> + <annotation cp="🐂" type="tts">ox</annotation> + <annotation cp="🐃">buffalo | water</annotation> + <annotation cp="🐃" type="tts">water buffalo</annotation> + <annotation cp="🐄">cow</annotation> + <annotation cp="🐄" type="tts">cow</annotation> + <annotation cp="🐷">face | pig</annotation> + <annotation cp="🐷" type="tts">pig face</annotation> + <annotation cp="🐖">pig | sow</annotation> + <annotation cp="🐖" type="tts">pig</annotation> + <annotation cp="🐗">boar | pig</annotation> + <annotation cp="🐗" type="tts">boar</annotation> + <annotation cp="🐽">face | nose | pig</annotation> + <annotation cp="🐽" type="tts">pig nose</annotation> + <annotation cp="🐏">Aries | male | ram | sheep | zodiac</annotation> + <annotation cp="🐏" type="tts">ram</annotation> + <annotation cp="🐑">ewe | female | sheep</annotation> + <annotation cp="🐑" type="tts">ewe</annotation> + <annotation cp="🐐">Capricorn | goat | zodiac</annotation> + <annotation cp="🐐" type="tts">goat</annotation> + <annotation cp="🐪">camel | dromedary | hump</annotation> + <annotation cp="🐪" type="tts">camel</annotation> + <annotation cp="🐫">bactrian | camel | hump | two-hump camel</annotation> + <annotation cp="🐫" type="tts">two-hump camel</annotation> + <annotation cp="🦙">alpaca | guanaco | llama | vicuña | wool</annotation> + <annotation cp="🦙" type="tts">llama</annotation> + <annotation cp="🦒">giraffe | spots</annotation> + <annotation cp="🦒" type="tts">giraffe</annotation> + <annotation cp="🐘">elephant</annotation> + <annotation cp="🐘" type="tts">elephant</annotation> + <annotation cp="🦣">extinction | large | mammoth | tusk | woolly</annotation> + <annotation cp="🦣" type="tts">mammoth</annotation> + <annotation cp="🦏">rhinoceros</annotation> + <annotation cp="🦏" type="tts">rhinoceros</annotation> + <annotation cp="🦛">hippo | hippopotamus</annotation> + <annotation cp="🦛" type="tts">hippopotamus</annotation> + <annotation cp="🐭">face | mouse</annotation> + <annotation cp="🐭" type="tts">mouse face</annotation> + <annotation cp="🐁">mouse</annotation> + <annotation cp="🐁" type="tts">mouse</annotation> + <annotation cp="🐀">rat</annotation> + <annotation cp="🐀" type="tts">rat</annotation> + <annotation cp="🐹">face | hamster | pet</annotation> + <annotation cp="🐹" type="tts">hamster</annotation> + <annotation cp="🐰">bunny | face | pet | rabbit</annotation> + <annotation cp="🐰" type="tts">rabbit face</annotation> + <annotation cp="🐇">bunny | pet | rabbit</annotation> + <annotation cp="🐇" type="tts">rabbit</annotation> + <annotation cp="🐿">chipmunk | squirrel</annotation> + <annotation cp="🐿" type="tts">chipmunk</annotation> + <annotation cp="🦫">beaver | dam</annotation> + <annotation cp="🦫" type="tts">beaver</annotation> + <annotation cp="🦔">hedgehog | spiny</annotation> + <annotation cp="🦔" type="tts">hedgehog</annotation> + <annotation cp="🦇">bat | vampire</annotation> + <annotation cp="🦇" type="tts">bat</annotation> + <annotation cp="🐻">bear | face</annotation> + <annotation cp="🐻" type="tts">bear</annotation> + <annotation cp="🐻❄">arctic | bear | polar bear | white</annotation> <!-- 1F43B 200D 2744 --> + <annotation cp="🐻❄" type="tts">polar bear</annotation> + <annotation cp="🐨">bear | koala</annotation> + <annotation cp="🐨" type="tts">koala</annotation> + <annotation cp="🐼">face | panda</annotation> + <annotation cp="🐼" type="tts">panda</annotation> + <annotation cp="🦥">lazy | sloth | slow</annotation> + <annotation cp="🦥" type="tts">sloth</annotation> + <annotation cp="🦦">fishing | otter | playful</annotation> + <annotation cp="🦦" type="tts">otter</annotation> + <annotation cp="🦨">skunk | stink</annotation> + <annotation cp="🦨" type="tts">skunk</annotation> + <annotation cp="🦘">Australia | joey | jump | kangaroo | marsupial</annotation> + <annotation cp="🦘" type="tts">kangaroo</annotation> + <annotation cp="🦡">badger | honey badger | pester</annotation> + <annotation cp="🦡" type="tts">badger</annotation> + <annotation cp="🐾">feet | paw | paw prints | print</annotation> + <annotation cp="🐾" type="tts">paw prints</annotation> + <annotation cp="🦃">bird | turkey</annotation> + <annotation cp="🦃" type="tts">turkey</annotation> + <annotation cp="🐔">bird | chicken</annotation> + <annotation cp="🐔" type="tts">chicken</annotation> + <annotation cp="🐓">bird | rooster</annotation> + <annotation cp="🐓" type="tts">rooster</annotation> + <annotation cp="🐣">baby | bird | chick | hatching</annotation> + <annotation cp="🐣" type="tts">hatching chick</annotation> + <annotation cp="🐤">baby | bird | chick</annotation> + <annotation cp="🐤" type="tts">baby chick</annotation> + <annotation cp="🐥">baby | bird | chick | front-facing baby chick</annotation> + <annotation cp="🐥" type="tts">front-facing baby chick</annotation> + <annotation cp="🐦">bird</annotation> + <annotation cp="🐦" type="tts">bird</annotation> + <annotation cp="🐧">bird | penguin</annotation> + <annotation cp="🐧" type="tts">penguin</annotation> + <annotation cp="🕊">bird | dove | fly | peace</annotation> + <annotation cp="🕊" type="tts">dove</annotation> + <annotation cp="🦅">bird | eagle</annotation> + <annotation cp="🦅" type="tts">eagle</annotation> + <annotation cp="🦆">bird | duck</annotation> + <annotation cp="🦆" type="tts">duck</annotation> + <annotation cp="🦢">bird | cygnet | swan | ugly duckling</annotation> + <annotation cp="🦢" type="tts">swan</annotation> + <annotation cp="🦉">bird | owl | wise</annotation> + <annotation cp="🦉" type="tts">owl</annotation> + <annotation cp="🦤">dodo | extinction | large | Mauritius</annotation> + <annotation cp="🦤" type="tts">dodo</annotation> + <annotation cp="🪶">bird | feather | flight | light | plumage</annotation> + <annotation cp="🪶" type="tts">feather</annotation> + <annotation cp="🦩">flamboyant | flamingo | tropical</annotation> + <annotation cp="🦩" type="tts">flamingo</annotation> + <annotation cp="🦚">bird | ostentatious | peacock | peahen | proud</annotation> + <annotation cp="🦚" type="tts">peacock</annotation> + <annotation cp="🦜">bird | parrot | pirate | talk</annotation> + <annotation cp="🦜" type="tts">parrot</annotation> + <annotation cp="🐸">face | frog</annotation> + <annotation cp="🐸" type="tts">frog</annotation> + <annotation cp="🐊">crocodile</annotation> + <annotation cp="🐊" type="tts">crocodile</annotation> + <annotation cp="🐢">terrapin | tortoise | turtle</annotation> + <annotation cp="🐢" type="tts">turtle</annotation> + <annotation cp="🦎">lizard | reptile</annotation> + <annotation cp="🦎" type="tts">lizard</annotation> + <annotation cp="🐍">bearer | Ophiuchus | serpent | snake | zodiac</annotation> + <annotation cp="🐍" type="tts">snake</annotation> + <annotation cp="🐲">dragon | face | fairy tale</annotation> + <annotation cp="🐲" type="tts">dragon face</annotation> + <annotation cp="🐉">dragon | fairy tale</annotation> + <annotation cp="🐉" type="tts">dragon</annotation> + <annotation cp="🦕">brachiosaurus | brontosaurus | diplodocus | sauropod</annotation> + <annotation cp="🦕" type="tts">sauropod</annotation> + <annotation cp="🦖">T-Rex | Tyrannosaurus Rex</annotation> + <annotation cp="🦖" type="tts">T-Rex</annotation> + <annotation cp="🐳">face | spouting | whale</annotation> + <annotation cp="🐳" type="tts">spouting whale</annotation> + <annotation cp="🐋">whale</annotation> + <annotation cp="🐋" type="tts">whale</annotation> + <annotation cp="🐬">dolphin | flipper</annotation> + <annotation cp="🐬" type="tts">dolphin</annotation> + <annotation cp="🦭">sea Lion | seal</annotation> + <annotation cp="🦭" type="tts">seal</annotation> + <annotation cp="🐟">fish | Pisces | zodiac</annotation> + <annotation cp="🐟" type="tts">fish</annotation> + <annotation cp="🐠">fish | tropical</annotation> + <annotation cp="🐠" type="tts">tropical fish</annotation> + <annotation cp="🐡">blowfish | fish</annotation> + <annotation cp="🐡" type="tts">blowfish</annotation> + <annotation cp="🦈">fish | shark</annotation> + <annotation cp="🦈" type="tts">shark</annotation> + <annotation cp="🐙">octopus</annotation> + <annotation cp="🐙" type="tts">octopus</annotation> + <annotation cp="🐚">shell | spiral</annotation> + <annotation cp="🐚" type="tts">spiral shell</annotation> + <annotation cp="🐌">snail</annotation> + <annotation cp="🐌" type="tts">snail</annotation> + <annotation cp="🦋">butterfly | insect | pretty</annotation> + <annotation cp="🦋" type="tts">butterfly</annotation> + <annotation cp="🐛">bug | insect</annotation> + <annotation cp="🐛" type="tts">bug</annotation> + <annotation cp="🐜">ant | insect</annotation> + <annotation cp="🐜" type="tts">ant</annotation> + <annotation cp="🐝">bee | honeybee | insect</annotation> + <annotation cp="🐝" type="tts">honeybee</annotation> + <annotation cp="🪲">beetle | bug | insect</annotation> + <annotation cp="🪲" type="tts">beetle</annotation> + <annotation cp="🐞">beetle | insect | lady beetle | ladybird | ladybug</annotation> + <annotation cp="🐞" type="tts">lady beetle</annotation> + <annotation cp="🦗">cricket | grasshopper</annotation> + <annotation cp="🦗" type="tts">cricket</annotation> + <annotation cp="🪳">cockroach | insect | pest | roach</annotation> + <annotation cp="🪳" type="tts">cockroach</annotation> + <annotation cp="🕷">insect | spider</annotation> + <annotation cp="🕷" type="tts">spider</annotation> + <annotation cp="🕸">spider | web</annotation> + <annotation cp="🕸" type="tts">spider web</annotation> + <annotation cp="🦂">scorpio | Scorpio | scorpion | zodiac</annotation> + <annotation cp="🦂" type="tts">scorpion</annotation> + <annotation cp="🦟">disease | fever | malaria | mosquito | pest | virus</annotation> + <annotation cp="🦟" type="tts">mosquito</annotation> + <annotation cp="🪰">disease | fly | maggot | pest | rotting</annotation> + <annotation cp="🪰" type="tts">fly</annotation> + <annotation cp="🪱">annelid | earthworm | parasite | worm</annotation> + <annotation cp="🪱" type="tts">worm</annotation> + <annotation cp="🦠">amoeba | bacteria | microbe | virus</annotation> + <annotation cp="🦠" type="tts">microbe</annotation> + <annotation cp="💐">bouquet | flower</annotation> + <annotation cp="💐" type="tts">bouquet</annotation> + <annotation cp="🌸">blossom | cherry | flower</annotation> + <annotation cp="🌸" type="tts">cherry blossom</annotation> + <annotation cp="💮">flower | white flower</annotation> + <annotation cp="💮" type="tts">white flower</annotation> + <annotation cp="🏵">plant | rosette</annotation> + <annotation cp="🏵" type="tts">rosette</annotation> + <annotation cp="🌹">flower | rose</annotation> + <annotation cp="🌹" type="tts">rose</annotation> + <annotation cp="🥀">flower | wilted</annotation> + <annotation cp="🥀" type="tts">wilted flower</annotation> + <annotation cp="🌺">flower | hibiscus</annotation> + <annotation cp="🌺" type="tts">hibiscus</annotation> + <annotation cp="🌻">flower | sun | sunflower</annotation> + <annotation cp="🌻" type="tts">sunflower</annotation> + <annotation cp="🌼">blossom | flower</annotation> + <annotation cp="🌼" type="tts">blossom</annotation> + <annotation cp="🌷">flower | tulip</annotation> + <annotation cp="🌷" type="tts">tulip</annotation> + <annotation cp="🌱">seedling | young</annotation> + <annotation cp="🌱" type="tts">seedling</annotation> + <annotation cp="🪴">boring | grow | house | nurturing | plant | potted plant | useless</annotation> + <annotation cp="🪴" type="tts">potted plant</annotation> + <annotation cp="🌲">evergreen tree | tree</annotation> + <annotation cp="🌲" type="tts">evergreen tree</annotation> + <annotation cp="🌳">deciduous | shedding | tree</annotation> + <annotation cp="🌳" type="tts">deciduous tree</annotation> + <annotation cp="🌴">palm | tree</annotation> + <annotation cp="🌴" type="tts">palm tree</annotation> + <annotation cp="🌵">cactus | plant</annotation> + <annotation cp="🌵" type="tts">cactus</annotation> + <annotation cp="🌾">ear | grain | rice | sheaf of rice</annotation> + <annotation cp="🌾" type="tts">sheaf of rice</annotation> + <annotation cp="🌿">herb | leaf</annotation> + <annotation cp="🌿" type="tts">herb</annotation> + <annotation cp="☘">plant | shamrock</annotation> + <annotation cp="☘" type="tts">shamrock</annotation> + <annotation cp="🍀">4 | clover | four | four-leaf clover | leaf</annotation> + <annotation cp="🍀" type="tts">four leaf clover</annotation> + <annotation cp="🍁">falling | leaf | maple</annotation> + <annotation cp="🍁" type="tts">maple leaf</annotation> + <annotation cp="🍂">fallen leaf | falling | leaf</annotation> + <annotation cp="🍂" type="tts">fallen leaf</annotation> + <annotation cp="🍃">blow | flutter | leaf | leaf fluttering in wind | wind</annotation> + <annotation cp="🍃" type="tts">leaf fluttering in wind</annotation> + <annotation cp="🍇">fruit | grape | grapes</annotation> + <annotation cp="🍇" type="tts">grapes</annotation> + <annotation cp="🍈">fruit | melon</annotation> + <annotation cp="🍈" type="tts">melon</annotation> + <annotation cp="🍉">fruit | watermelon</annotation> + <annotation cp="🍉" type="tts">watermelon</annotation> + <annotation cp="🍊">fruit | orange | tangerine</annotation> + <annotation cp="🍊" type="tts">tangerine</annotation> + <annotation cp="🍋">citrus | fruit | lemon</annotation> + <annotation cp="🍋" type="tts">lemon</annotation> + <annotation cp="🍌">banana | fruit</annotation> + <annotation cp="🍌" type="tts">banana</annotation> + <annotation cp="🍍">fruit | pineapple</annotation> + <annotation cp="🍍" type="tts">pineapple</annotation> + <annotation cp="🥭">fruit | mango | tropical</annotation> + <annotation cp="🥭" type="tts">mango</annotation> + <annotation cp="🍎">apple | fruit | red</annotation> + <annotation cp="🍎" type="tts">red apple</annotation> + <annotation cp="🍏">apple | fruit | green</annotation> + <annotation cp="🍏" type="tts">green apple</annotation> + <annotation cp="🍐">fruit | pear</annotation> + <annotation cp="🍐" type="tts">pear</annotation> + <annotation cp="🍑">fruit | peach</annotation> + <annotation cp="🍑" type="tts">peach</annotation> + <annotation cp="🍒">berries | cherries | cherry | fruit | red</annotation> + <annotation cp="🍒" type="tts">cherries</annotation> + <annotation cp="🍓">berry | fruit | strawberry</annotation> + <annotation cp="🍓" type="tts">strawberry</annotation> + <annotation cp="🫐">berry | bilberry | blue | blueberries | blueberry</annotation> + <annotation cp="🫐" type="tts">blueberries</annotation> + <annotation cp="🥝">food | fruit | kiwi</annotation> + <annotation cp="🥝" type="tts">kiwi fruit</annotation> + <annotation cp="🍅">fruit | tomato | vegetable</annotation> + <annotation cp="🍅" type="tts">tomato</annotation> + <annotation cp="🫒">food | olive</annotation> + <annotation cp="🫒" type="tts">olive</annotation> + <annotation cp="🥥">coconut | palm | piña colada</annotation> + <annotation cp="🥥" type="tts">coconut</annotation> + <annotation cp="🥑">avocado | food | fruit</annotation> + <annotation cp="🥑" type="tts">avocado</annotation> + <annotation cp="🍆">aubergine | eggplant | vegetable</annotation> + <annotation cp="🍆" type="tts">eggplant</annotation> + <annotation cp="🥔">food | potato | vegetable</annotation> + <annotation cp="🥔" type="tts">potato</annotation> + <annotation cp="🥕">carrot | food | vegetable</annotation> + <annotation cp="🥕" type="tts">carrot</annotation> + <annotation cp="🌽">corn | ear | ear of corn | maize | maze</annotation> + <annotation cp="🌽" type="tts">ear of corn</annotation> + <annotation cp="🌶">hot | pepper</annotation> + <annotation cp="🌶" type="tts">hot pepper</annotation> + <annotation cp="🫑">bell pepper | capsicum | pepper | vegetable</annotation> + <annotation cp="🫑" type="tts">bell pepper</annotation> + <annotation cp="🥒">cucumber | food | pickle | vegetable</annotation> + <annotation cp="🥒" type="tts">cucumber</annotation> + <annotation cp="🥬">bok choy | cabbage | kale | leafy green | lettuce</annotation> + <annotation cp="🥬" type="tts">leafy green</annotation> + <annotation cp="🥦">broccoli | wild cabbage</annotation> + <annotation cp="🥦" type="tts">broccoli</annotation> + <annotation cp="🧄">flavoring | garlic</annotation> + <annotation cp="🧄" type="tts">garlic</annotation> + <annotation cp="🧅">flavoring | onion</annotation> + <annotation cp="🧅" type="tts">onion</annotation> + <annotation cp="🍄">mushroom | toadstool</annotation> + <annotation cp="🍄" type="tts">mushroom</annotation> + <annotation cp="🥜">food | nut | peanut | peanuts | vegetable</annotation> + <annotation cp="🥜" type="tts">peanuts</annotation> + <annotation cp="🌰">chestnut | plant</annotation> + <annotation cp="🌰" type="tts">chestnut</annotation> + <annotation cp="🍞">bread | loaf</annotation> + <annotation cp="🍞" type="tts">bread</annotation> + <annotation cp="🥐">bread | breakfast | croissant | food | french | roll</annotation> + <annotation cp="🥐" type="tts">croissant</annotation> + <annotation cp="🥖">baguette | bread | food | french</annotation> + <annotation cp="🥖" type="tts">baguette bread</annotation> + <annotation cp="🫓">arepa | flatbread | lavash | naan | pita</annotation> + <annotation cp="🫓" type="tts">flatbread</annotation> + <annotation cp="🥨">pretzel | twisted</annotation> + <annotation cp="🥨" type="tts">pretzel</annotation> + <annotation cp="🥯">bagel | bakery | breakfast | schmear</annotation> + <annotation cp="🥯" type="tts">bagel</annotation> + <annotation cp="🥞">breakfast | crêpe | food | hotcake | pancake | pancakes</annotation> + <annotation cp="🥞" type="tts">pancakes</annotation> + <annotation cp="🧇">breakfast | indecisive | iron | waffle</annotation> + <annotation cp="🧇" type="tts">waffle</annotation> + <annotation cp="🧀">cheese | cheese wedge</annotation> + <annotation cp="🧀" type="tts">cheese wedge</annotation> + <annotation cp="🍖">bone | meat | meat on bone</annotation> + <annotation cp="🍖" type="tts">meat on bone</annotation> + <annotation cp="🍗">bone | chicken | drumstick | leg | poultry</annotation> + <annotation cp="🍗" type="tts">poultry leg</annotation> + <annotation cp="🥩">chop | cut of meat | lambchop | porkchop | steak</annotation> + <annotation cp="🥩" type="tts">cut of meat</annotation> + <annotation cp="🥓">bacon | breakfast | food | meat</annotation> + <annotation cp="🥓" type="tts">bacon</annotation> + <annotation cp="🍔">burger | hamburger</annotation> + <annotation cp="🍔" type="tts">hamburger</annotation> + <annotation cp="🍟">french | fries</annotation> + <annotation cp="🍟" type="tts">french fries</annotation> + <annotation cp="🍕">cheese | pizza | slice</annotation> + <annotation cp="🍕" type="tts">pizza</annotation> + <annotation cp="🌭">frankfurter | hot dog | hotdog | sausage</annotation> + <annotation cp="🌭" type="tts">hot dog</annotation> + <annotation cp="🥪">bread | sandwich</annotation> + <annotation cp="🥪" type="tts">sandwich</annotation> + <annotation cp="🌮">mexican | taco</annotation> + <annotation cp="🌮" type="tts">taco</annotation> + <annotation cp="🌯">burrito | mexican | wrap</annotation> + <annotation cp="🌯" type="tts">burrito</annotation> + <annotation cp="🫔">mexican | tamale | wrapped</annotation> + <annotation cp="🫔" type="tts">tamale</annotation> + <annotation cp="🥙">falafel | flatbread | food | gyro | kebab | stuffed</annotation> + <annotation cp="🥙" type="tts">stuffed flatbread</annotation> + <annotation cp="🧆">chickpea | falafel | meatball</annotation> + <annotation cp="🧆" type="tts">falafel</annotation> + <annotation cp="🥚">breakfast | egg | food</annotation> + <annotation cp="🥚" type="tts">egg</annotation> + <annotation cp="🍳">breakfast | cooking | egg | frying | pan</annotation> + <annotation cp="🍳" type="tts">cooking</annotation> + <annotation cp="🥘">casserole | food | paella | pan | shallow | shallow pan of food</annotation> + <annotation cp="🥘" type="tts">shallow pan of food</annotation> + <annotation cp="🍲">pot | pot of food | stew</annotation> + <annotation cp="🍲" type="tts">pot of food</annotation> + <annotation cp="🫕">cheese | chocolate | fondue | melted | pot | Swiss</annotation> + <annotation cp="🫕" type="tts">fondue</annotation> + <annotation cp="🥣">bowl with spoon | breakfast | cereal | congee</annotation> + <annotation cp="🥣" type="tts">bowl with spoon</annotation> + <annotation cp="🥗">food | green | salad</annotation> + <annotation cp="🥗" type="tts">green salad</annotation> + <annotation cp="🍿">popcorn</annotation> + <annotation cp="🍿" type="tts">popcorn</annotation> + <annotation cp="🧈">butter | dairy</annotation> + <annotation cp="🧈" type="tts">butter</annotation> + <annotation cp="🧂">condiment | salt | shaker</annotation> + <annotation cp="🧂" type="tts">salt</annotation> + <annotation cp="🥫">can | canned food</annotation> + <annotation cp="🥫" type="tts">canned food</annotation> + <annotation cp="🍱">bento | box</annotation> + <annotation cp="🍱" type="tts">bento box</annotation> + <annotation cp="🍘">cracker | rice</annotation> + <annotation cp="🍘" type="tts">rice cracker</annotation> + <annotation cp="🍙">ball | Japanese | rice</annotation> + <annotation cp="🍙" type="tts">rice ball</annotation> + <annotation cp="🍚">cooked | rice</annotation> + <annotation cp="🍚" type="tts">cooked rice</annotation> + <annotation cp="🍛">curry | rice</annotation> + <annotation cp="🍛" type="tts">curry rice</annotation> + <annotation cp="🍜">bowl | noodle | ramen | steaming</annotation> + <annotation cp="🍜" type="tts">steaming bowl</annotation> + <annotation cp="🍝">pasta | spaghetti</annotation> + <annotation cp="🍝" type="tts">spaghetti</annotation> + <annotation cp="🍠">potato | roasted | sweet</annotation> + <annotation cp="🍠" type="tts">roasted sweet potato</annotation> + <annotation cp="🍢">kebab | oden | seafood | skewer | stick</annotation> + <annotation cp="🍢" type="tts">oden</annotation> + <annotation cp="🍣">sushi</annotation> + <annotation cp="🍣" type="tts">sushi</annotation> + <annotation cp="🍤">fried | prawn | shrimp | tempura</annotation> + <annotation cp="🍤" type="tts">fried shrimp</annotation> + <annotation cp="🍥">cake | fish | fish cake with swirl | pastry | swirl</annotation> + <annotation cp="🍥" type="tts">fish cake with swirl</annotation> + <annotation cp="🥮">autumn | festival | moon cake | yuèbǐng</annotation> + <annotation cp="🥮" type="tts">moon cake</annotation> + <annotation cp="🍡">dango | dessert | Japanese | skewer | stick | sweet</annotation> + <annotation cp="🍡" type="tts">dango</annotation> + <annotation cp="🥟">dumpling | empanada | gyōza | jiaozi | pierogi | potsticker</annotation> + <annotation cp="🥟" type="tts">dumpling</annotation> + <annotation cp="🥠">fortune cookie | prophecy</annotation> + <annotation cp="🥠" type="tts">fortune cookie</annotation> + <annotation cp="🥡">oyster pail | takeout box</annotation> + <annotation cp="🥡" type="tts">takeout box</annotation> + <annotation cp="🦀">Cancer | crab | zodiac</annotation> + <annotation cp="🦀" type="tts">crab</annotation> + <annotation cp="🦞">bisque | claws | lobster | seafood</annotation> + <annotation cp="🦞" type="tts">lobster</annotation> + <annotation cp="🦐">food | shellfish | shrimp | small</annotation> + <annotation cp="🦐" type="tts">shrimp</annotation> + <annotation cp="🦑">food | molusc | squid</annotation> + <annotation cp="🦑" type="tts">squid</annotation> + <annotation cp="🦪">diving | oyster | pearl</annotation> + <annotation cp="🦪" type="tts">oyster</annotation> + <annotation cp="🍦">cream | dessert | ice | icecream | soft | sweet</annotation> + <annotation cp="🍦" type="tts">soft ice cream</annotation> + <annotation cp="🍧">dessert | ice | shaved | sweet</annotation> + <annotation cp="🍧" type="tts">shaved ice</annotation> + <annotation cp="🍨">cream | dessert | ice | sweet</annotation> + <annotation cp="🍨" type="tts">ice cream</annotation> + <annotation cp="🍩">breakfast | dessert | donut | doughnut | sweet</annotation> + <annotation cp="🍩" type="tts">doughnut</annotation> + <annotation cp="🍪">cookie | dessert | sweet</annotation> + <annotation cp="🍪" type="tts">cookie</annotation> + <annotation cp="🎂">birthday | cake | celebration | dessert | pastry | sweet</annotation> + <annotation cp="🎂" type="tts">birthday cake</annotation> + <annotation cp="🍰">cake | dessert | pastry | shortcake | slice | sweet</annotation> + <annotation cp="🍰" type="tts">shortcake</annotation> + <annotation cp="🧁">bakery | cupcake | sweet</annotation> + <annotation cp="🧁" type="tts">cupcake</annotation> + <annotation cp="🥧">filling | pastry | pie</annotation> + <annotation cp="🥧" type="tts">pie</annotation> + <annotation cp="🍫">bar | chocolate | dessert | sweet</annotation> + <annotation cp="🍫" type="tts">chocolate bar</annotation> + <annotation cp="🍬">candy | dessert | sweet</annotation> + <annotation cp="🍬" type="tts">candy</annotation> + <annotation cp="🍭">candy | dessert | lollipop | sweet</annotation> + <annotation cp="🍭" type="tts">lollipop</annotation> + <annotation cp="🍮">custard | dessert | pudding | sweet</annotation> + <annotation cp="🍮" type="tts">custard</annotation> + <annotation cp="🍯">honey | honeypot | pot | sweet</annotation> + <annotation cp="🍯" type="tts">honey pot</annotation> + <annotation cp="🍼">baby | bottle | drink | milk</annotation> + <annotation cp="🍼" type="tts">baby bottle</annotation> + <annotation cp="🥛">drink | glass | glass of milk | milk</annotation> + <annotation cp="🥛" type="tts">glass of milk</annotation> + <annotation cp="☕">beverage | coffee | drink | hot | steaming | tea</annotation> + <annotation cp="☕" type="tts">hot beverage</annotation> + <annotation cp="🫖">drink | pot | tea | teapot</annotation> + <annotation cp="🫖" type="tts">teapot</annotation> + <annotation cp="🍵">beverage | cup | drink | tea | teacup | teacup without handle</annotation> + <annotation cp="🍵" type="tts">teacup without handle</annotation> + <annotation cp="🍶">bar | beverage | bottle | cup | drink | sake</annotation> + <annotation cp="🍶" type="tts">sake</annotation> + <annotation cp="🍾">bar | bottle | bottle with popping cork | cork | drink | popping</annotation> + <annotation cp="🍾" type="tts">bottle with popping cork</annotation> + <annotation cp="🍷">bar | beverage | drink | glass | wine</annotation> + <annotation cp="🍷" type="tts">wine glass</annotation> + <annotation cp="🍸">bar | cocktail | drink | glass</annotation> + <annotation cp="🍸" type="tts">cocktail glass</annotation> + <annotation cp="🍹">bar | drink | tropical</annotation> + <annotation cp="🍹" type="tts">tropical drink</annotation> + <annotation cp="🍺">bar | beer | drink | mug</annotation> + <annotation cp="🍺" type="tts">beer mug</annotation> + <annotation cp="🍻">bar | beer | clink | clinking beer mugs | drink | mug</annotation> + <annotation cp="🍻" type="tts">clinking beer mugs</annotation> + <annotation cp="🥂">celebrate | clink | clinking glasses | drink | glass</annotation> + <annotation cp="🥂" type="tts">clinking glasses</annotation> + <annotation cp="🥃">glass | liquor | shot | tumbler | whisky</annotation> + <annotation cp="🥃" type="tts">tumbler glass</annotation> + <annotation cp="🥤">cup with straw | juice | soda</annotation> + <annotation cp="🥤" type="tts">cup with straw</annotation> + <annotation cp="🧋">bubble | milk | pearl | tea</annotation> + <annotation cp="🧋" type="tts">bubble tea</annotation> + <annotation cp="🧃">beverage | box | juice | straw | sweet</annotation> + <annotation cp="🧃" type="tts">beverage box</annotation> + <annotation cp="🧉">drink | mate</annotation> + <annotation cp="🧉" type="tts">mate</annotation> + <annotation cp="🧊">cold | ice | ice cube | iceberg</annotation> + <annotation cp="🧊" type="tts">ice</annotation> + <annotation cp="🥢">chopsticks | hashi</annotation> + <annotation cp="🥢" type="tts">chopsticks</annotation> + <annotation cp="🍽">cooking | fork | fork and knife with plate | knife | plate</annotation> + <annotation cp="🍽" type="tts">fork and knife with plate</annotation> + <annotation cp="🍴">cooking | cutlery | fork | fork and knife | knife</annotation> + <annotation cp="🍴" type="tts">fork and knife</annotation> + <annotation cp="🥄">spoon | tableware</annotation> + <annotation cp="🥄" type="tts">spoon</annotation> + <annotation cp="🔪">cooking | hocho | kitchen knife | knife | tool | weapon</annotation> + <annotation cp="🔪" type="tts">kitchen knife</annotation> + <annotation cp="🏺">amphora | Aquarius | cooking | drink | jug | zodiac</annotation> + <annotation cp="🏺" type="tts">amphora</annotation> + <annotation cp="🌍">Africa | earth | Europe | globe | globe showing Europe-Africa | world</annotation> + <annotation cp="🌍" type="tts">globe showing Europe-Africa</annotation> + <annotation cp="🌎">Americas | earth | globe | globe showing Americas | world</annotation> + <annotation cp="🌎" type="tts">globe showing Americas</annotation> + <annotation cp="🌏">Asia | Australia | earth | globe | globe showing Asia-Australia | world</annotation> + <annotation cp="🌏" type="tts">globe showing Asia-Australia</annotation> + <annotation cp="🌐">earth | globe | globe with meridians | meridians | world</annotation> + <annotation cp="🌐" type="tts">globe with meridians</annotation> + <annotation cp="🗺">map | world</annotation> + <annotation cp="🗺" type="tts">world map</annotation> + <annotation cp="🗾">Japan | map | map of Japan</annotation> + <annotation cp="🗾" type="tts">map of Japan</annotation> + <annotation cp="🧭">compass | magnetic | navigation | orienteering</annotation> + <annotation cp="🧭" type="tts">compass</annotation> + <annotation cp="🏔">cold | mountain | snow | snow-capped mountain</annotation> + <annotation cp="🏔" type="tts">snow-capped mountain</annotation> + <annotation cp="⛰">mountain</annotation> + <annotation cp="⛰" type="tts">mountain</annotation> + <annotation cp="🌋">eruption | mountain | volcano</annotation> + <annotation cp="🌋" type="tts">volcano</annotation> + <annotation cp="🗻">fuji | mount fuji | mountain</annotation> + <annotation cp="🗻" type="tts">mount fuji</annotation> + <annotation cp="🏕">camping</annotation> + <annotation cp="🏕" type="tts">camping</annotation> + <annotation cp="🏖">beach | beach with umbrella | umbrella</annotation> + <annotation cp="🏖" type="tts">beach with umbrella</annotation> + <annotation cp="🏜">desert</annotation> + <annotation cp="🏜" type="tts">desert</annotation> + <annotation cp="🏝">desert | island</annotation> + <annotation cp="🏝" type="tts">desert island</annotation> + <annotation cp="🏞">national park | park</annotation> + <annotation cp="🏞" type="tts">national park</annotation> + <annotation cp="🏟">stadium</annotation> + <annotation cp="🏟" type="tts">stadium</annotation> + <annotation cp="🏛">classical | classical building</annotation> + <annotation cp="🏛" type="tts">classical building</annotation> + <annotation cp="🏗">building construction | construction</annotation> + <annotation cp="🏗" type="tts">building construction</annotation> + <annotation cp="🧱">brick | bricks | clay | mortar | wall</annotation> + <annotation cp="🧱" type="tts">brick</annotation> + <annotation cp="🪨">boulder | heavy | rock | solid | stone</annotation> + <annotation cp="🪨" type="tts">rock</annotation> + <annotation cp="🪵">log | lumber | timber | wood</annotation> + <annotation cp="🪵" type="tts">wood</annotation> + <annotation cp="🛖">house | hut | roundhouse | yurt</annotation> + <annotation cp="🛖" type="tts">hut</annotation> + <annotation cp="🏘">houses</annotation> + <annotation cp="🏘" type="tts">houses</annotation> + <annotation cp="🏚">derelict | house</annotation> + <annotation cp="🏚" type="tts">derelict house</annotation> + <annotation cp="🏠">home | house</annotation> + <annotation cp="🏠" type="tts">house</annotation> + <annotation cp="🏡">garden | home | house | house with garden</annotation> + <annotation cp="🏡" type="tts">house with garden</annotation> + <annotation cp="🏢">building | office building</annotation> + <annotation cp="🏢" type="tts">office building</annotation> + <annotation cp="🏣">Japanese | Japanese post office | post</annotation> + <annotation cp="🏣" type="tts">Japanese post office</annotation> + <annotation cp="🏤">European | post | post office</annotation> + <annotation cp="🏤" type="tts">post office</annotation> + <annotation cp="🏥">doctor | hospital | medicine</annotation> + <annotation cp="🏥" type="tts">hospital</annotation> + <annotation cp="🏦">bank | building</annotation> + <annotation cp="🏦" type="tts">bank</annotation> + <annotation cp="🏨">building | hotel</annotation> + <annotation cp="🏨" type="tts">hotel</annotation> + <annotation cp="🏩">hotel | love</annotation> + <annotation cp="🏩" type="tts">love hotel</annotation> + <annotation cp="🏪">convenience | store</annotation> + <annotation cp="🏪" type="tts">convenience store</annotation> + <annotation cp="🏫">building | school</annotation> + <annotation cp="🏫" type="tts">school</annotation> + <annotation cp="🏬">department | store</annotation> + <annotation cp="🏬" type="tts">department store</annotation> + <annotation cp="🏭">building | factory</annotation> + <annotation cp="🏭" type="tts">factory</annotation> + <annotation cp="🏯">castle | Japanese</annotation> + <annotation cp="🏯" type="tts">Japanese castle</annotation> + <annotation cp="🏰">castle | European</annotation> + <annotation cp="🏰" type="tts">castle</annotation> + <annotation cp="💒">chapel | romance | wedding</annotation> + <annotation cp="💒" type="tts">wedding</annotation> + <annotation cp="🗼">Tokyo | tower</annotation> + <annotation cp="🗼" type="tts">Tokyo tower</annotation> + <annotation cp="🗽">liberty | statue | Statue of Liberty</annotation> + <annotation cp="🗽" type="tts">Statue of Liberty</annotation> + <annotation cp="⛪">Christian | church | cross | religion</annotation> + <annotation cp="⛪" type="tts">church</annotation> + <annotation cp="🕌">islam | mosque | Muslim | religion</annotation> + <annotation cp="🕌" type="tts">mosque</annotation> + <annotation cp="🛕">hindu | temple</annotation> + <annotation cp="🛕" type="tts">hindu temple</annotation> + <annotation cp="🕍">Jew | Jewish | religion | synagogue | temple</annotation> + <annotation cp="🕍" type="tts">synagogue</annotation> + <annotation cp="⛩">religion | shinto | shrine</annotation> + <annotation cp="⛩" type="tts">shinto shrine</annotation> + <annotation cp="🕋">islam | kaaba | Muslim | religion</annotation> + <annotation cp="🕋" type="tts">kaaba</annotation> + <annotation cp="⛲">fountain</annotation> + <annotation cp="⛲" type="tts">fountain</annotation> + <annotation cp="⛺">camping | tent</annotation> + <annotation cp="⛺" type="tts">tent</annotation> + <annotation cp="🌁">fog | foggy</annotation> + <annotation cp="🌁" type="tts">foggy</annotation> + <annotation cp="🌃">night | night with stars | star</annotation> + <annotation cp="🌃" type="tts">night with stars</annotation> + <annotation cp="🏙">city | cityscape</annotation> + <annotation cp="🏙" type="tts">cityscape</annotation> + <annotation cp="🌄">morning | mountain | sun | sunrise | sunrise over mountains</annotation> + <annotation cp="🌄" type="tts">sunrise over mountains</annotation> + <annotation cp="🌅">morning | sun | sunrise</annotation> + <annotation cp="🌅" type="tts">sunrise</annotation> + <annotation cp="🌆">city | cityscape at dusk | dusk | evening | landscape | sunset</annotation> + <annotation cp="🌆" type="tts">cityscape at dusk</annotation> + <annotation cp="🌇">dusk | sun | sunset</annotation> + <annotation cp="🌇" type="tts">sunset</annotation> + <annotation cp="🌉">bridge | bridge at night | night</annotation> + <annotation cp="🌉" type="tts">bridge at night</annotation> + <annotation cp="♨">hot | hotsprings | springs | steaming</annotation> + <annotation cp="♨" type="tts">hot springs</annotation> + <annotation cp="🎠">carousel | horse</annotation> + <annotation cp="🎠" type="tts">carousel horse</annotation> + <annotation cp="🎡">amusement park | ferris | wheel</annotation> + <annotation cp="🎡" type="tts">ferris wheel</annotation> + <annotation cp="🎢">amusement park | coaster | roller</annotation> + <annotation cp="🎢" type="tts">roller coaster</annotation> + <annotation cp="💈">barber | haircut | pole</annotation> + <annotation cp="💈" type="tts">barber pole</annotation> + <annotation cp="🎪">circus | tent</annotation> + <annotation cp="🎪" type="tts">circus tent</annotation> + <annotation cp="🚂">engine | locomotive | railway | steam | train</annotation> + <annotation cp="🚂" type="tts">locomotive</annotation> + <annotation cp="🚃">car | electric | railway | train | tram | trolleybus</annotation> + <annotation cp="🚃" type="tts">railway car</annotation> + <annotation cp="🚄">high-speed train | railway | shinkansen | speed | train</annotation> + <annotation cp="🚄" type="tts">high-speed train</annotation> + <annotation cp="🚅">bullet | railway | shinkansen | speed | train</annotation> + <annotation cp="🚅" type="tts">bullet train</annotation> + <annotation cp="🚆">railway | train</annotation> + <annotation cp="🚆" type="tts">train</annotation> + <annotation cp="🚇">metro | subway</annotation> + <annotation cp="🚇" type="tts">metro</annotation> + <annotation cp="🚈">light rail | railway</annotation> + <annotation cp="🚈" type="tts">light rail</annotation> + <annotation cp="🚉">railway | station | train</annotation> + <annotation cp="🚉" type="tts">station</annotation> + <annotation cp="🚊">tram | trolleybus</annotation> + <annotation cp="🚊" type="tts">tram</annotation> + <annotation cp="🚝">monorail | vehicle</annotation> + <annotation cp="🚝" type="tts">monorail</annotation> + <annotation cp="🚞">car | mountain | railway</annotation> + <annotation cp="🚞" type="tts">mountain railway</annotation> + <annotation cp="🚋">car | tram | trolleybus</annotation> + <annotation cp="🚋" type="tts">tram car</annotation> + <annotation cp="🚌">bus | vehicle</annotation> + <annotation cp="🚌" type="tts">bus</annotation> + <annotation cp="🚍">bus | oncoming</annotation> + <annotation cp="🚍" type="tts">oncoming bus</annotation> + <annotation cp="🚎">bus | tram | trolley | trolleybus</annotation> + <annotation cp="🚎" type="tts">trolleybus</annotation> + <annotation cp="🚐">bus | minibus</annotation> + <annotation cp="🚐" type="tts">minibus</annotation> + <annotation cp="🚑">ambulance | vehicle</annotation> + <annotation cp="🚑" type="tts">ambulance</annotation> + <annotation cp="🚒">engine | fire | truck</annotation> + <annotation cp="🚒" type="tts">fire engine</annotation> + <annotation cp="🚓">car | patrol | police</annotation> + <annotation cp="🚓" type="tts">police car</annotation> + <annotation cp="🚔">car | oncoming | police</annotation> + <annotation cp="🚔" type="tts">oncoming police car</annotation> + <annotation cp="🚕">taxi | vehicle</annotation> + <annotation cp="🚕" type="tts">taxi</annotation> + <annotation cp="🚖">oncoming | taxi</annotation> + <annotation cp="🚖" type="tts">oncoming taxi</annotation> + <annotation cp="🚗">automobile | car</annotation> + <annotation cp="🚗" type="tts">automobile</annotation> + <annotation cp="🚘">automobile | car | oncoming</annotation> + <annotation cp="🚘" type="tts">oncoming automobile</annotation> + <annotation cp="🚙">recreational | sport utility | sport utility vehicle</annotation> + <annotation cp="🚙" type="tts">sport utility vehicle</annotation> + <annotation cp="🛻">pick-up | pickup | truck</annotation> + <annotation cp="🛻" type="tts">pickup truck</annotation> + <annotation cp="🚚">delivery | truck</annotation> + <annotation cp="🚚" type="tts">delivery truck</annotation> + <annotation cp="🚛">articulated lorry | lorry | semi | truck</annotation> + <annotation cp="🚛" type="tts">articulated lorry</annotation> + <annotation cp="🚜">tractor | vehicle</annotation> + <annotation cp="🚜" type="tts">tractor</annotation> + <annotation cp="🏎">car | racing</annotation> + <annotation cp="🏎" type="tts">racing car</annotation> + <annotation cp="🏍">motorcycle | racing</annotation> + <annotation cp="🏍" type="tts">motorcycle</annotation> + <annotation cp="🛵">motor | scooter</annotation> + <annotation cp="🛵" type="tts">motor scooter</annotation> + <annotation cp="🦽">accessibility | manual wheelchair</annotation> + <annotation cp="🦽" type="tts">manual wheelchair</annotation> + <annotation cp="🦼">accessibility | motorized wheelchair</annotation> + <annotation cp="🦼" type="tts">motorized wheelchair</annotation> + <annotation cp="🛺">auto rickshaw | tuk tuk</annotation> + <annotation cp="🛺" type="tts">auto rickshaw</annotation> + <annotation cp="🚲">bicycle | bike</annotation> + <annotation cp="🚲" type="tts">bicycle</annotation> + <annotation cp="🛴">kick | scooter</annotation> + <annotation cp="🛴" type="tts">kick scooter</annotation> + <annotation cp="🛹">board | skateboard</annotation> + <annotation cp="🛹" type="tts">skateboard</annotation> + <annotation cp="🛼">roller | skate</annotation> + <annotation cp="🛼" type="tts">roller skate</annotation> + <annotation cp="🚏">bus | busstop | stop</annotation> + <annotation cp="🚏" type="tts">bus stop</annotation> + <annotation cp="🛣">highway | motorway | road</annotation> + <annotation cp="🛣" type="tts">motorway</annotation> + <annotation cp="🛤">railway | railway track | train</annotation> + <annotation cp="🛤" type="tts">railway track</annotation> + <annotation cp="🛢">drum | oil</annotation> + <annotation cp="🛢" type="tts">oil drum</annotation> + <annotation cp="⛽">diesel | fuel | fuelpump | gas | pump | station</annotation> + <annotation cp="⛽" type="tts">fuel pump</annotation> + <annotation cp="🚨">beacon | car | light | police | revolving</annotation> + <annotation cp="🚨" type="tts">police car light</annotation> + <annotation cp="🚥">horizontal traffic light | light | signal | traffic</annotation> + <annotation cp="🚥" type="tts">horizontal traffic light</annotation> + <annotation cp="🚦">light | signal | traffic | vertical traffic light</annotation> + <annotation cp="🚦" type="tts">vertical traffic light</annotation> + <annotation cp="🛑">octagonal | sign | stop</annotation> + <annotation cp="🛑" type="tts">stop sign</annotation> + <annotation cp="🚧">barrier | construction</annotation> + <annotation cp="🚧" type="tts">construction</annotation> + <annotation cp="⚓">anchor | ship | tool</annotation> + <annotation cp="⚓" type="tts">anchor</annotation> + <annotation cp="⛵">boat | resort | sailboat | sea | yacht</annotation> + <annotation cp="⛵" type="tts">sailboat</annotation> + <annotation cp="🛶">boat | canoe</annotation> + <annotation cp="🛶" type="tts">canoe</annotation> + <annotation cp="🚤">boat | speedboat</annotation> + <annotation cp="🚤" type="tts">speedboat</annotation> + <annotation cp="🛳">passenger | ship</annotation> + <annotation cp="🛳" type="tts">passenger ship</annotation> + <annotation cp="⛴">boat | ferry | passenger</annotation> + <annotation cp="⛴" type="tts">ferry</annotation> + <annotation cp="🛥">boat | motor boat | motorboat</annotation> + <annotation cp="🛥" type="tts">motor boat</annotation> + <annotation cp="🚢">boat | passenger | ship</annotation> + <annotation cp="🚢" type="tts">ship</annotation> + <annotation cp="✈">aeroplane | airplane</annotation> + <annotation cp="✈" type="tts">airplane</annotation> + <annotation cp="🛩">aeroplane | airplane | small airplane</annotation> + <annotation cp="🛩" type="tts">small airplane</annotation> + <annotation cp="🛫">aeroplane | airplane | check-in | departure | departures</annotation> + <annotation cp="🛫" type="tts">airplane departure</annotation> + <annotation cp="🛬">aeroplane | airplane | airplane arrival | arrivals | arriving | landing</annotation> + <annotation cp="🛬" type="tts">airplane arrival</annotation> + <annotation cp="🪂">hang-glide | parachute | parasail | skydive</annotation> + <annotation cp="🪂" type="tts">parachute</annotation> + <annotation cp="💺">chair | seat</annotation> + <annotation cp="💺" type="tts">seat</annotation> + <annotation cp="🚁">helicopter | vehicle</annotation> + <annotation cp="🚁" type="tts">helicopter</annotation> + <annotation cp="🚟">railway | suspension</annotation> + <annotation cp="🚟" type="tts">suspension railway</annotation> + <annotation cp="🚠">cable | gondola | mountain | mountain cableway</annotation> + <annotation cp="🚠" type="tts">mountain cableway</annotation> + <annotation cp="🚡">aerial | cable | car | gondola | tramway</annotation> + <annotation cp="🚡" type="tts">aerial tramway</annotation> + <annotation cp="🛰">satellite | space</annotation> + <annotation cp="🛰" type="tts">satellite</annotation> + <annotation cp="🚀">rocket | space</annotation> + <annotation cp="🚀" type="tts">rocket</annotation> + <annotation cp="🛸">flying saucer | UFO</annotation> + <annotation cp="🛸" type="tts">flying saucer</annotation> + <annotation cp="🛎">bell | bellhop | hotel</annotation> + <annotation cp="🛎" type="tts">bellhop bell</annotation> + <annotation cp="🧳">luggage | packing | travel</annotation> + <annotation cp="🧳" type="tts">luggage</annotation> + <annotation cp="⌛">hourglass done | sand | timer</annotation> + <annotation cp="⌛" type="tts">hourglass done</annotation> + <annotation cp="⏳">hourglass | hourglass not done | sand | timer</annotation> + <annotation cp="⏳" type="tts">hourglass not done</annotation> + <annotation cp="⌚">clock | watch</annotation> + <annotation cp="⌚" type="tts">watch</annotation> + <annotation cp="⏰">alarm | clock</annotation> + <annotation cp="⏰" type="tts">alarm clock</annotation> + <annotation cp="⏱">clock | stopwatch</annotation> + <annotation cp="⏱" type="tts">stopwatch</annotation> + <annotation cp="⏲">clock | timer</annotation> + <annotation cp="⏲" type="tts">timer clock</annotation> + <annotation cp="🕰">clock | mantelpiece clock</annotation> + <annotation cp="🕰" type="tts">mantelpiece clock</annotation> + <annotation cp="🕛">00 | 12 | 12:00 | clock | o’clock | twelve</annotation> + <annotation cp="🕛" type="tts">twelve o’clock</annotation> + <annotation cp="🕧">12 | 12:30 | clock | thirty | twelve | twelve-thirty</annotation> + <annotation cp="🕧" type="tts">twelve-thirty</annotation> + <annotation cp="🕐">00 | 1 | 1:00 | clock | o’clock | one</annotation> + <annotation cp="🕐" type="tts">one o’clock</annotation> + <annotation cp="🕜">1 | 1:30 | clock | one | one-thirty | thirty</annotation> + <annotation cp="🕜" type="tts">one-thirty</annotation> + <annotation cp="🕑">00 | 2 | 2:00 | clock | o’clock | two</annotation> + <annotation cp="🕑" type="tts">two o’clock</annotation> + <annotation cp="🕝">2 | 2:30 | clock | thirty | two | two-thirty</annotation> + <annotation cp="🕝" type="tts">two-thirty</annotation> + <annotation cp="🕒">00 | 3 | 3:00 | clock | o’clock | three</annotation> + <annotation cp="🕒" type="tts">three o’clock</annotation> + <annotation cp="🕞">3 | 3:30 | clock | thirty | three | three-thirty</annotation> + <annotation cp="🕞" type="tts">three-thirty</annotation> + <annotation cp="🕓">00 | 4 | 4:00 | clock | four | o’clock</annotation> + <annotation cp="🕓" type="tts">four o’clock</annotation> + <annotation cp="🕟">4 | 4:30 | clock | four | four-thirty | thirty</annotation> + <annotation cp="🕟" type="tts">four-thirty</annotation> + <annotation cp="🕔">00 | 5 | 5:00 | clock | five | o’clock</annotation> + <annotation cp="🕔" type="tts">five o’clock</annotation> + <annotation cp="🕠">5 | 5:30 | clock | five | five-thirty | thirty</annotation> + <annotation cp="🕠" type="tts">five-thirty</annotation> + <annotation cp="🕕">00 | 6 | 6:00 | clock | o’clock | six</annotation> + <annotation cp="🕕" type="tts">six o’clock</annotation> + <annotation cp="🕡">6 | 6:30 | clock | six | six-thirty | thirty</annotation> + <annotation cp="🕡" type="tts">six-thirty</annotation> + <annotation cp="🕖">00 | 7 | 7:00 | clock | o’clock | seven</annotation> + <annotation cp="🕖" type="tts">seven o’clock</annotation> + <annotation cp="🕢">7 | 7:30 | clock | seven | seven-thirty | thirty</annotation> + <annotation cp="🕢" type="tts">seven-thirty</annotation> + <annotation cp="🕗">00 | 8 | 8:00 | clock | eight | o’clock</annotation> + <annotation cp="🕗" type="tts">eight o’clock</annotation> + <annotation cp="🕣">8 | 8:30 | clock | eight | eight-thirty | thirty</annotation> + <annotation cp="🕣" type="tts">eight-thirty</annotation> + <annotation cp="🕘">00 | 9 | 9:00 | clock | nine | o’clock</annotation> + <annotation cp="🕘" type="tts">nine o’clock</annotation> + <annotation cp="🕤">9 | 9:30 | clock | nine | nine-thirty | thirty</annotation> + <annotation cp="🕤" type="tts">nine-thirty</annotation> + <annotation cp="🕙">00 | 10 | 10:00 | clock | o’clock | ten</annotation> + <annotation cp="🕙" type="tts">ten o’clock</annotation> + <annotation cp="🕥">10 | 10:30 | clock | ten | ten-thirty | thirty</annotation> + <annotation cp="🕥" type="tts">ten-thirty</annotation> + <annotation cp="🕚">00 | 11 | 11:00 | clock | eleven | o’clock</annotation> + <annotation cp="🕚" type="tts">eleven o’clock</annotation> + <annotation cp="🕦">11 | 11:30 | clock | eleven | eleven-thirty | thirty</annotation> + <annotation cp="🕦" type="tts">eleven-thirty</annotation> + <annotation cp="🌑">dark | moon | new moon</annotation> + <annotation cp="🌑" type="tts">new moon</annotation> + <annotation cp="🌒">crescent | moon | waxing</annotation> + <annotation cp="🌒" type="tts">waxing crescent moon</annotation> + <annotation cp="🌓">first quarter moon | moon | quarter</annotation> + <annotation cp="🌓" type="tts">first quarter moon</annotation> + <annotation cp="🌔">gibbous | moon | waxing</annotation> + <annotation cp="🌔" type="tts">waxing gibbous moon</annotation> + <annotation cp="🌕">full | moon</annotation> + <annotation cp="🌕" type="tts">full moon</annotation> + <annotation cp="🌖">gibbous | moon | waning</annotation> + <annotation cp="🌖" type="tts">waning gibbous moon</annotation> + <annotation cp="🌗">last quarter moon | moon | quarter</annotation> + <annotation cp="🌗" type="tts">last quarter moon</annotation> + <annotation cp="🌘">crescent | moon | waning</annotation> + <annotation cp="🌘" type="tts">waning crescent moon</annotation> + <annotation cp="🌙">crescent | moon</annotation> + <annotation cp="🌙" type="tts">crescent moon</annotation> + <annotation cp="🌚">face | moon | new moon face</annotation> + <annotation cp="🌚" type="tts">new moon face</annotation> + <annotation cp="🌛">face | first quarter moon face | moon | quarter</annotation> + <annotation cp="🌛" type="tts">first quarter moon face</annotation> + <annotation cp="🌜">face | last quarter moon face | moon | quarter</annotation> + <annotation cp="🌜" type="tts">last quarter moon face</annotation> + <annotation cp="🌡">thermometer | weather</annotation> + <annotation cp="🌡" type="tts">thermometer</annotation> + <annotation cp="☀">bright | rays | sun | sunny</annotation> + <annotation cp="☀" type="tts">sun</annotation> + <annotation cp="🌝">bright | face | full | moon</annotation> + <annotation cp="🌝" type="tts">full moon face</annotation> + <annotation cp="🌞">bright | face | sun | sun with face</annotation> + <annotation cp="🌞" type="tts">sun with face</annotation> + <annotation cp="🪐">ringed planet | saturn | saturnine</annotation> + <annotation cp="🪐" type="tts">ringed planet</annotation> + <annotation cp="⭐">star</annotation> + <annotation cp="⭐" type="tts">star</annotation> + <annotation cp="🌟">glittery | glow | glowing star | shining | sparkle | star</annotation> + <annotation cp="🌟" type="tts">glowing star</annotation> + <annotation cp="🌠">falling | shooting | star</annotation> + <annotation cp="🌠" type="tts">shooting star</annotation> + <annotation cp="🌌">milky way | space</annotation> + <annotation cp="🌌" type="tts">milky way</annotation> + <annotation cp="☁">cloud | weather</annotation> + <annotation cp="☁" type="tts">cloud</annotation> + <annotation cp="⛅">cloud | sun | sun behind cloud</annotation> + <annotation cp="⛅" type="tts">sun behind cloud</annotation> + <annotation cp="⛈">cloud | cloud with lightning and rain | rain | thunder</annotation> + <annotation cp="⛈" type="tts">cloud with lightning and rain</annotation> + <annotation cp="🌤">cloud | sun | sun behind small cloud</annotation> + <annotation cp="🌤" type="tts">sun behind small cloud</annotation> + <annotation cp="🌥">cloud | sun | sun behind large cloud</annotation> + <annotation cp="🌥" type="tts">sun behind large cloud</annotation> + <annotation cp="🌦">cloud | rain | sun | sun behind rain cloud</annotation> + <annotation cp="🌦" type="tts">sun behind rain cloud</annotation> + <annotation cp="🌧">cloud | cloud with rain | rain</annotation> + <annotation cp="🌧" type="tts">cloud with rain</annotation> + <annotation cp="🌨">cloud | cloud with snow | cold | snow</annotation> + <annotation cp="🌨" type="tts">cloud with snow</annotation> + <annotation cp="🌩">cloud | cloud with lightning | lightning</annotation> + <annotation cp="🌩" type="tts">cloud with lightning</annotation> + <annotation cp="🌪">cloud | tornado | whirlwind</annotation> + <annotation cp="🌪" type="tts">tornado</annotation> + <annotation cp="🌫">cloud | fog</annotation> + <annotation cp="🌫" type="tts">fog</annotation> + <annotation cp="🌬">blow | cloud | face | wind</annotation> + <annotation cp="🌬" type="tts">wind face</annotation> + <annotation cp="🌀">cyclone | dizzy | hurricane | twister | typhoon</annotation> + <annotation cp="🌀" type="tts">cyclone</annotation> + <annotation cp="🌈">rain | rainbow</annotation> + <annotation cp="🌈" type="tts">rainbow</annotation> + <annotation cp="🌂">closed umbrella | clothing | rain | umbrella</annotation> + <annotation cp="🌂" type="tts">closed umbrella</annotation> + <annotation cp="☂">clothing | rain | umbrella</annotation> + <annotation cp="☂" type="tts">umbrella</annotation> + <annotation cp="☔">clothing | drop | rain | umbrella | umbrella with rain drops</annotation> + <annotation cp="☔" type="tts">umbrella with rain drops</annotation> + <annotation cp="⛱">rain | sun | umbrella | umbrella on ground</annotation> + <annotation cp="⛱" type="tts">umbrella on ground</annotation> + <annotation cp="⚡">danger | electric | high voltage | lightning | voltage | zap</annotation> + <annotation cp="⚡" type="tts">high voltage</annotation> + <annotation cp="❄">cold | snow | snowflake</annotation> + <annotation cp="❄" type="tts">snowflake</annotation> + <annotation cp="☃">cold | snow | snowman</annotation> + <annotation cp="☃" type="tts">snowman</annotation> + <annotation cp="⛄">cold | snow | snowman | snowman without snow</annotation> + <annotation cp="⛄" type="tts">snowman without snow</annotation> + <annotation cp="☄">comet | space</annotation> + <annotation cp="☄" type="tts">comet</annotation> + <annotation cp="🔥">fire | flame | tool</annotation> + <annotation cp="🔥" type="tts">fire</annotation> + <annotation cp="💧">cold | comic | drop | droplet | sweat</annotation> + <annotation cp="💧" type="tts">droplet</annotation> + <annotation cp="🌊">ocean | water | wave</annotation> + <annotation cp="🌊" type="tts">water wave</annotation> + <annotation cp="🎃">celebration | halloween | jack | jack-o-lantern | lantern</annotation> + <annotation cp="🎃" type="tts">jack-o-lantern</annotation> + <annotation cp="🎄">celebration | Christmas | tree</annotation> + <annotation cp="🎄" type="tts">Christmas tree</annotation> + <annotation cp="🎆">celebration | fireworks</annotation> + <annotation cp="🎆" type="tts">fireworks</annotation> + <annotation cp="🎇">celebration | fireworks | sparkle | sparkler</annotation> + <annotation cp="🎇" type="tts">sparkler</annotation> + <annotation cp="🧨">dynamite | explosive | firecracker | fireworks</annotation> + <annotation cp="🧨" type="tts">firecracker</annotation> + <annotation cp="✨">* | sparkle | sparkles | star</annotation> + <annotation cp="✨" type="tts">sparkles</annotation> + <annotation cp="🎈">balloon | celebration</annotation> + <annotation cp="🎈" type="tts">balloon</annotation> + <annotation cp="🎉">celebration | party | popper | tada</annotation> + <annotation cp="🎉" type="tts">party popper</annotation> + <annotation cp="🎊">ball | celebration | confetti</annotation> + <annotation cp="🎊" type="tts">confetti ball</annotation> + <annotation cp="🎋">banner | celebration | Japanese | tanabata tree | tree</annotation> + <annotation cp="🎋" type="tts">tanabata tree</annotation> + <annotation cp="🎍">bamboo | celebration | Japanese | pine | pine decoration</annotation> + <annotation cp="🎍" type="tts">pine decoration</annotation> + <annotation cp="🎎">celebration | doll | festival | Japanese | Japanese dolls</annotation> + <annotation cp="🎎" type="tts">Japanese dolls</annotation> + <annotation cp="🎏">carp | celebration | streamer</annotation> + <annotation cp="🎏" type="tts">carp streamer</annotation> + <annotation cp="🎐">bell | celebration | chime | wind</annotation> + <annotation cp="🎐" type="tts">wind chime</annotation> + <annotation cp="🎑">celebration | ceremony | moon | moon viewing ceremony</annotation> + <annotation cp="🎑" type="tts">moon viewing ceremony</annotation> + <annotation cp="🧧">gift | good luck | hóngbāo | lai see | money | red envelope</annotation> + <annotation cp="🧧" type="tts">red envelope</annotation> + <annotation cp="🎀">celebration | ribbon</annotation> + <annotation cp="🎀" type="tts">ribbon</annotation> + <annotation cp="🎁">box | celebration | gift | present | wrapped</annotation> + <annotation cp="🎁" type="tts">wrapped gift</annotation> + <annotation cp="🎗">celebration | reminder | ribbon</annotation> + <annotation cp="🎗" type="tts">reminder ribbon</annotation> + <annotation cp="🎟">admission | admission tickets | ticket</annotation> + <annotation cp="🎟" type="tts">admission tickets</annotation> + <annotation cp="🎫">admission | ticket</annotation> + <annotation cp="🎫" type="tts">ticket</annotation> + <annotation cp="🎖">celebration | medal | military</annotation> + <annotation cp="🎖" type="tts">military medal</annotation> + <annotation cp="🏆">prize | trophy</annotation> + <annotation cp="🏆" type="tts">trophy</annotation> + <annotation cp="🏅">medal | sports medal</annotation> + <annotation cp="🏅" type="tts">sports medal</annotation> + <annotation cp="🥇">1st place medal | first | gold | medal</annotation> + <annotation cp="🥇" type="tts">1st place medal</annotation> + <annotation cp="🥈">2nd place medal | medal | second | silver</annotation> + <annotation cp="🥈" type="tts">2nd place medal</annotation> + <annotation cp="🥉">3rd place medal | bronze | medal | third</annotation> + <annotation cp="🥉" type="tts">3rd place medal</annotation> + <annotation cp="⚽">ball | football | soccer</annotation> + <annotation cp="⚽" type="tts">soccer ball</annotation> + <annotation cp="⚾">ball | baseball</annotation> + <annotation cp="⚾" type="tts">baseball</annotation> + <annotation cp="🥎">ball | glove | softball | underarm</annotation> + <annotation cp="🥎" type="tts">softball</annotation> + <annotation cp="🏀">ball | basketball | hoop</annotation> + <annotation cp="🏀" type="tts">basketball</annotation> + <annotation cp="🏐">ball | game | volleyball</annotation> + <annotation cp="🏐" type="tts">volleyball</annotation> + <annotation cp="🏈">american | ball | football</annotation> + <annotation cp="🏈" type="tts">american football</annotation> + <annotation cp="🏉">ball | football | rugby</annotation> + <annotation cp="🏉" type="tts">rugby football</annotation> + <annotation cp="🎾">ball | racquet | tennis</annotation> + <annotation cp="🎾" type="tts">tennis</annotation> + <annotation cp="🥏">flying disc | ultimate</annotation> + <annotation cp="🥏" type="tts">flying disc</annotation> + <annotation cp="🎳">ball | bowling | game</annotation> + <annotation cp="🎳" type="tts">bowling</annotation> + <annotation cp="🏏">ball | bat | cricket game | game</annotation> + <annotation cp="🏏" type="tts">cricket game</annotation> + <annotation cp="🏑">ball | field | game | hockey | stick</annotation> + <annotation cp="🏑" type="tts">field hockey</annotation> + <annotation cp="🏒">game | hockey | ice | puck | stick</annotation> + <annotation cp="🏒" type="tts">ice hockey</annotation> + <annotation cp="🥍">ball | goal | lacrosse | stick</annotation> + <annotation cp="🥍" type="tts">lacrosse</annotation> + <annotation cp="🏓">ball | bat | game | paddle | ping pong | table tennis</annotation> + <annotation cp="🏓" type="tts">ping pong</annotation> + <annotation cp="🏸">badminton | birdie | game | racquet | shuttlecock</annotation> + <annotation cp="🏸" type="tts">badminton</annotation> + <annotation cp="🥊">boxing | glove</annotation> + <annotation cp="🥊" type="tts">boxing glove</annotation> + <annotation cp="🥋">judo | karate | martial arts | martial arts uniform | taekwondo | uniform</annotation> + <annotation cp="🥋" type="tts">martial arts uniform</annotation> + <annotation cp="🥅">goal | net</annotation> + <annotation cp="🥅" type="tts">goal net</annotation> + <annotation cp="⛳">flag in hole | golf | hole</annotation> + <annotation cp="⛳" type="tts">flag in hole</annotation> + <annotation cp="⛸">ice | skate</annotation> + <annotation cp="⛸" type="tts">ice skate</annotation> + <annotation cp="🎣">fish | fishing pole | pole</annotation> + <annotation cp="🎣" type="tts">fishing pole</annotation> + <annotation cp="🤿">diving | diving mask | scuba | snorkeling</annotation> + <annotation cp="🤿" type="tts">diving mask</annotation> + <annotation cp="🎽">athletics | running | sash | shirt</annotation> + <annotation cp="🎽" type="tts">running shirt</annotation> + <annotation cp="🎿">ski | skis | snow</annotation> + <annotation cp="🎿" type="tts">skis</annotation> + <annotation cp="🛷">sled | sledge | sleigh</annotation> + <annotation cp="🛷" type="tts">sled</annotation> + <annotation cp="🥌">curling stone | game | rock</annotation> + <annotation cp="🥌" type="tts">curling stone</annotation> + <annotation cp="🎯">bullseye | dart | direct hit | game | hit | target</annotation> + <annotation cp="🎯" type="tts">bullseye</annotation> + <annotation cp="🪀">fluctuate | toy | yo-yo</annotation> + <annotation cp="🪀" type="tts">yo-yo</annotation> + <annotation cp="🪁">fly | kite | soar</annotation> + <annotation cp="🪁" type="tts">kite</annotation> + <annotation cp="🎱">8 | ball | billiard | eight | game | pool 8 ball</annotation> + <annotation cp="🎱" type="tts">pool 8 ball</annotation> + <annotation cp="🔮">ball | crystal | fairy tale | fantasy | fortune | tool</annotation> + <annotation cp="🔮" type="tts">crystal ball</annotation> + <annotation cp="🪄">magic | magic wand | witch | wizard</annotation> + <annotation cp="🪄" type="tts">magic wand</annotation> + <annotation cp="🧿">bead | charm | evil-eye | nazar | nazar amulet | talisman</annotation> + <annotation cp="🧿" type="tts">nazar amulet</annotation> + <annotation cp="🎮">controller | game | video game</annotation> + <annotation cp="🎮" type="tts">video game</annotation> + <annotation cp="🕹">game | joystick | video game</annotation> + <annotation cp="🕹" type="tts">joystick</annotation> + <annotation cp="🎰">game | slot | slot machine</annotation> + <annotation cp="🎰" type="tts">slot machine</annotation> + <annotation cp="🎲">dice | die | game</annotation> + <annotation cp="🎲" type="tts">game die</annotation> + <annotation cp="🧩">clue | interlocking | jigsaw | piece | puzzle</annotation> + <annotation cp="🧩" type="tts">puzzle piece</annotation> + <annotation cp="🧸">plaything | plush | stuffed | teddy bear | toy</annotation> + <annotation cp="🧸" type="tts">teddy bear</annotation> + <annotation cp="🪅">celebration | party | piñata</annotation> + <annotation cp="🪅" type="tts">piñata</annotation> + <annotation cp="🪆">doll | nesting | nesting dolls | russia</annotation> + <annotation cp="🪆" type="tts">nesting dolls</annotation> + <annotation cp="♠">card | game | spade suit</annotation> + <annotation cp="♠" type="tts">spade suit</annotation> + <annotation cp="♥">card | game | heart suit</annotation> + <annotation cp="♥" type="tts">heart suit</annotation> + <annotation cp="♦">card | diamond suit | game</annotation> + <annotation cp="♦" type="tts">diamond suit</annotation> + <annotation cp="♣">card | club suit | game</annotation> + <annotation cp="♣" type="tts">club suit</annotation> + <annotation cp="♟">chess | chess pawn | dupe | expendable</annotation> + <annotation cp="♟" type="tts">chess pawn</annotation> + <annotation cp="🃏">card | game | joker | wildcard</annotation> + <annotation cp="🃏" type="tts">joker</annotation> + <annotation cp="🀄">game | mahjong | mahjong red dragon | red</annotation> + <annotation cp="🀄" type="tts">mahjong red dragon</annotation> + <annotation cp="🎴">card | flower | flower playing cards | game | Japanese | playing</annotation> + <annotation cp="🎴" type="tts">flower playing cards</annotation> + <annotation cp="🎭">art | mask | performing | performing arts | theater | theatre</annotation> + <annotation cp="🎭" type="tts">performing arts</annotation> + <annotation cp="🖼">art | frame | framed picture | museum | painting | picture</annotation> + <annotation cp="🖼" type="tts">framed picture</annotation> + <annotation cp="🎨">art | artist palette | museum | painting | palette</annotation> + <annotation cp="🎨" type="tts">artist palette</annotation> + <annotation cp="🧵">needle | sewing | spool | string | thread</annotation> + <annotation cp="🧵" type="tts">thread</annotation> + <annotation cp="🪡">embroidery | needle | sewing | stitches | sutures | tailoring</annotation> + <annotation cp="🪡" type="tts">sewing needle</annotation> + <annotation cp="🧶">ball | crochet | knit | yarn</annotation> + <annotation cp="🧶" type="tts">yarn</annotation> + <annotation cp="🪢">knot | rope | tangled | tie | twine | twist</annotation> + <annotation cp="🪢" type="tts">knot</annotation> + <annotation cp="👓">clothing | eye | eyeglasses | eyewear | glasses</annotation> + <annotation cp="👓" type="tts">glasses</annotation> + <annotation cp="🕶">dark | eye | eyewear | glasses | sunglasses</annotation> + <annotation cp="🕶" type="tts">sunglasses</annotation> + <annotation cp="🥽">eye protection | goggles | swimming | welding</annotation> + <annotation cp="🥽" type="tts">goggles</annotation> + <annotation cp="🥼">doctor | experiment | lab coat | scientist</annotation> + <annotation cp="🥼" type="tts">lab coat</annotation> + <annotation cp="🦺">emergency | safety | vest</annotation> + <annotation cp="🦺" type="tts">safety vest</annotation> + <annotation cp="👔">clothing | necktie | tie</annotation> + <annotation cp="👔" type="tts">necktie</annotation> + <annotation cp="👕">clothing | shirt | t-shirt | tshirt</annotation> + <annotation cp="👕" type="tts">t-shirt</annotation> + <annotation cp="👖">clothing | jeans | pants | trousers</annotation> + <annotation cp="👖" type="tts">jeans</annotation> + <annotation cp="🧣">neck | scarf</annotation> + <annotation cp="🧣" type="tts">scarf</annotation> + <annotation cp="🧤">gloves | hand</annotation> + <annotation cp="🧤" type="tts">gloves</annotation> + <annotation cp="🧥">coat | jacket</annotation> + <annotation cp="🧥" type="tts">coat</annotation> + <annotation cp="🧦">socks | stocking</annotation> + <annotation cp="🧦" type="tts">socks</annotation> + <annotation cp="👗">clothing | dress</annotation> + <annotation cp="👗" type="tts">dress</annotation> + <annotation cp="👘">clothing | kimono</annotation> + <annotation cp="👘" type="tts">kimono</annotation> + <annotation cp="🥻">clothing | dress | sari</annotation> + <annotation cp="🥻" type="tts">sari</annotation> + <annotation cp="🩱">bathing suit | one-piece swimsuit</annotation> + <annotation cp="🩱" type="tts">one-piece swimsuit</annotation> + <annotation cp="🩲">bathing suit | briefs | one-piece | swimsuit | underwear</annotation> + <annotation cp="🩲" type="tts">briefs</annotation> + <annotation cp="🩳">bathing suit | pants | shorts | underwear</annotation> + <annotation cp="🩳" type="tts">shorts</annotation> + <annotation cp="👙">bikini | clothing | swim</annotation> + <annotation cp="👙" type="tts">bikini</annotation> + <annotation cp="👚">clothing | woman | woman’s clothes</annotation> + <annotation cp="👚" type="tts">woman’s clothes</annotation> + <annotation cp="👛">clothing | coin | purse</annotation> + <annotation cp="👛" type="tts">purse</annotation> + <annotation cp="👜">bag | clothing | handbag | purse</annotation> + <annotation cp="👜" type="tts">handbag</annotation> + <annotation cp="👝">bag | clothing | clutch bag | pouch</annotation> + <annotation cp="👝" type="tts">clutch bag</annotation> + <annotation cp="🛍">bag | hotel | shopping | shopping bags</annotation> + <annotation cp="🛍" type="tts">shopping bags</annotation> + <annotation cp="🎒">backpack | bag | rucksack | satchel | school</annotation> + <annotation cp="🎒" type="tts">backpack</annotation> + <annotation cp="🩴">beach sandals | sandals | thong sandal | thong sandals | thongs | zōri</annotation> + <annotation cp="🩴" type="tts">thong sandal</annotation> + <annotation cp="👞">clothing | man | man’s shoe | shoe</annotation> + <annotation cp="👞" type="tts">man’s shoe</annotation> + <annotation cp="👟">athletic | clothing | running shoe | shoe | sneaker</annotation> + <annotation cp="👟" type="tts">running shoe</annotation> + <annotation cp="🥾">backpacking | boot | camping | hiking</annotation> + <annotation cp="🥾" type="tts">hiking boot</annotation> + <annotation cp="🥿">ballet flat | flat shoe | slip-on | slipper</annotation> + <annotation cp="🥿" type="tts">flat shoe</annotation> + <annotation cp="👠">clothing | heel | high-heeled shoe | shoe | woman</annotation> + <annotation cp="👠" type="tts">high-heeled shoe</annotation> + <annotation cp="👡">clothing | sandal | shoe | woman | woman’s sandal</annotation> + <annotation cp="👡" type="tts">woman’s sandal</annotation> + <annotation cp="🩰">ballet | ballet shoes | dance</annotation> + <annotation cp="🩰" type="tts">ballet shoes</annotation> + <annotation cp="👢">boot | clothing | shoe | woman | woman’s boot</annotation> + <annotation cp="👢" type="tts">woman’s boot</annotation> + <annotation cp="👑">clothing | crown | king | queen</annotation> + <annotation cp="👑" type="tts">crown</annotation> + <annotation cp="👒">clothing | hat | woman | woman’s hat</annotation> + <annotation cp="👒" type="tts">woman’s hat</annotation> + <annotation cp="🎩">clothing | hat | top | tophat</annotation> + <annotation cp="🎩" type="tts">top hat</annotation> + <annotation cp="🎓">cap | celebration | clothing | graduation | hat</annotation> + <annotation cp="🎓" type="tts">graduation cap</annotation> + <annotation cp="🧢">baseball cap | billed cap</annotation> + <annotation cp="🧢" type="tts">billed cap</annotation> + <annotation cp="🪖">army | helmet | military | soldier | warrior</annotation> + <annotation cp="🪖" type="tts">military helmet</annotation> + <annotation cp="⛑">aid | cross | face | hat | helmet | rescue worker’s helmet</annotation> + <annotation cp="⛑" type="tts">rescue worker’s helmet</annotation> + <annotation cp="📿">beads | clothing | necklace | prayer | religion</annotation> + <annotation cp="📿" type="tts">prayer beads</annotation> + <annotation cp="💄">cosmetics | lipstick | makeup</annotation> + <annotation cp="💄" type="tts">lipstick</annotation> + <annotation cp="💍">diamond | ring</annotation> + <annotation cp="💍" type="tts">ring</annotation> + <annotation cp="💎">diamond | gem | gem stone | jewel</annotation> + <annotation cp="💎" type="tts">gem stone</annotation> + <annotation cp="🔇">mute | muted speaker | quiet | silent | speaker</annotation> + <annotation cp="🔇" type="tts">muted speaker</annotation> + <annotation cp="🔈">soft | speaker low volume</annotation> + <annotation cp="🔈" type="tts">speaker low volume</annotation> + <annotation cp="🔉">medium | speaker medium volume</annotation> + <annotation cp="🔉" type="tts">speaker medium volume</annotation> + <annotation cp="🔊">loud | speaker high volume</annotation> + <annotation cp="🔊" type="tts">speaker high volume</annotation> + <annotation cp="📢">loud | loudspeaker | public address</annotation> + <annotation cp="📢" type="tts">loudspeaker</annotation> + <annotation cp="📣">cheering | megaphone</annotation> + <annotation cp="📣" type="tts">megaphone</annotation> + <annotation cp="📯">horn | post | postal</annotation> + <annotation cp="📯" type="tts">postal horn</annotation> + <annotation cp="🔔">bell</annotation> + <annotation cp="🔔" type="tts">bell</annotation> + <annotation cp="🔕">bell | bell with slash | forbidden | mute | quiet | silent</annotation> + <annotation cp="🔕" type="tts">bell with slash</annotation> + <annotation cp="🎼">music | musical score | score</annotation> + <annotation cp="🎼" type="tts">musical score</annotation> + <annotation cp="🎵">music | musical note | note</annotation> + <annotation cp="🎵" type="tts">musical note</annotation> + <annotation cp="🎶">music | musical notes | note | notes</annotation> + <annotation cp="🎶" type="tts">musical notes</annotation> + <annotation cp="🎙">mic | microphone | music | studio</annotation> + <annotation cp="🎙" type="tts">studio microphone</annotation> + <annotation cp="🎚">level | music | slider</annotation> + <annotation cp="🎚" type="tts">level slider</annotation> + <annotation cp="🎛">control | knobs | music</annotation> + <annotation cp="🎛" type="tts">control knobs</annotation> + <annotation cp="🎤">karaoke | mic | microphone</annotation> + <annotation cp="🎤" type="tts">microphone</annotation> + <annotation cp="🎧">earbud | headphone</annotation> + <annotation cp="🎧" type="tts">headphone</annotation> + <annotation cp="📻">radio | video</annotation> + <annotation cp="📻" type="tts">radio</annotation> + <annotation cp="🎷">instrument | music | sax | saxophone</annotation> + <annotation cp="🎷" type="tts">saxophone</annotation> + <annotation cp="🪗">accordian | accordion | concertina | squeeze box</annotation> + <annotation cp="🪗" type="tts">accordion</annotation> + <annotation cp="🎸">guitar | instrument | music</annotation> + <annotation cp="🎸" type="tts">guitar</annotation> + <annotation cp="🎹">instrument | keyboard | music | musical keyboard | piano</annotation> + <annotation cp="🎹" type="tts">musical keyboard</annotation> + <annotation cp="🎺">instrument | music | trumpet</annotation> + <annotation cp="🎺" type="tts">trumpet</annotation> + <annotation cp="🎻">instrument | music | violin</annotation> + <annotation cp="🎻" type="tts">violin</annotation> + <annotation cp="🪕">banjo | music | stringed</annotation> + <annotation cp="🪕" type="tts">banjo</annotation> + <annotation cp="🥁">drum | drumsticks | music</annotation> + <annotation cp="🥁" type="tts">drum</annotation> + <annotation cp="🪘">beat | conga | drum | long drum | rhythm</annotation> + <annotation cp="🪘" type="tts">long drum</annotation> + <annotation cp="📱">cell | mobile | phone | telephone</annotation> + <annotation cp="📱" type="tts">mobile phone</annotation> + <annotation cp="📲">arrow | cell | mobile | mobile phone with arrow | phone | receive</annotation> + <annotation cp="📲" type="tts">mobile phone with arrow</annotation> + <annotation cp="☎">phone | telephone</annotation> + <annotation cp="☎" type="tts">telephone</annotation> + <annotation cp="📞">phone | receiver | telephone</annotation> + <annotation cp="📞" type="tts">telephone receiver</annotation> + <annotation cp="📟">pager</annotation> + <annotation cp="📟" type="tts">pager</annotation> + <annotation cp="📠">fax | fax machine</annotation> + <annotation cp="📠" type="tts">fax machine</annotation> + <annotation cp="🔋">battery</annotation> + <annotation cp="🔋" type="tts">battery</annotation> + <annotation cp="🔌">electric | electricity | plug</annotation> + <annotation cp="🔌" type="tts">electric plug</annotation> + <annotation cp="💻">computer | laptop | pc | personal</annotation> + <annotation cp="💻" type="tts">laptop</annotation> + <annotation cp="🖥">computer | desktop</annotation> + <annotation cp="🖥" type="tts">desktop computer</annotation> + <annotation cp="🖨">computer | printer</annotation> + <annotation cp="🖨" type="tts">printer</annotation> + <annotation cp="⌨">computer | keyboard</annotation> + <annotation cp="⌨" type="tts">keyboard</annotation> + <annotation cp="🖱">computer | computer mouse</annotation> + <annotation cp="🖱" type="tts">computer mouse</annotation> + <annotation cp="🖲">computer | trackball</annotation> + <annotation cp="🖲" type="tts">trackball</annotation> + <annotation cp="💽">computer | disk | minidisk | optical</annotation> + <annotation cp="💽" type="tts">computer disk</annotation> + <annotation cp="💾">computer | disk | floppy</annotation> + <annotation cp="💾" type="tts">floppy disk</annotation> + <annotation cp="💿">cd | computer | disk | optical</annotation> + <annotation cp="💿" type="tts">optical disk</annotation> + <annotation cp="📀">blu-ray | computer | disk | dvd | optical</annotation> + <annotation cp="📀" type="tts">dvd</annotation> + <annotation cp="🧮">abacus | calculation</annotation> + <annotation cp="🧮" type="tts">abacus</annotation> + <annotation cp="🎥">camera | cinema | movie</annotation> + <annotation cp="🎥" type="tts">movie camera</annotation> + <annotation cp="🎞">cinema | film | frames | movie</annotation> + <annotation cp="🎞" type="tts">film frames</annotation> + <annotation cp="📽">cinema | film | movie | projector | video</annotation> + <annotation cp="📽" type="tts">film projector</annotation> + <annotation cp="🎬">clapper | clapper board | movie</annotation> + <annotation cp="🎬" type="tts">clapper board</annotation> + <annotation cp="📺">television | tv | video</annotation> + <annotation cp="📺" type="tts">television</annotation> + <annotation cp="📷">camera | video</annotation> + <annotation cp="📷" type="tts">camera</annotation> + <annotation cp="📸">camera | camera with flash | flash | video</annotation> + <annotation cp="📸" type="tts">camera with flash</annotation> + <annotation cp="📹">camera | video</annotation> + <annotation cp="📹" type="tts">video camera</annotation> + <annotation cp="📼">tape | vhs | video | videocassette</annotation> + <annotation cp="📼" type="tts">videocassette</annotation> + <annotation cp="🔍">glass | magnifying | magnifying glass tilted left | search | tool</annotation> + <annotation cp="🔍" type="tts">magnifying glass tilted left</annotation> + <annotation cp="🔎">glass | magnifying | magnifying glass tilted right | search | tool</annotation> + <annotation cp="🔎" type="tts">magnifying glass tilted right</annotation> + <annotation cp="🕯">candle | light</annotation> + <annotation cp="🕯" type="tts">candle</annotation> + <annotation cp="💡">bulb | comic | electric | idea | light</annotation> + <annotation cp="💡" type="tts">light bulb</annotation> + <annotation cp="🔦">electric | flashlight | light | tool | torch</annotation> + <annotation cp="🔦" type="tts">flashlight</annotation> + <annotation cp="🏮">bar | lantern | light | red | red paper lantern</annotation> + <annotation cp="🏮" type="tts">red paper lantern</annotation> + <annotation cp="🪔">diya | lamp | oil</annotation> + <annotation cp="🪔" type="tts">diya lamp</annotation> + <annotation cp="📔">book | cover | decorated | notebook | notebook with decorative cover</annotation> + <annotation cp="📔" type="tts">notebook with decorative cover</annotation> + <annotation cp="📕">book | closed</annotation> + <annotation cp="📕" type="tts">closed book</annotation> + <annotation cp="📖">book | open</annotation> + <annotation cp="📖" type="tts">open book</annotation> + <annotation cp="📗">book | green</annotation> + <annotation cp="📗" type="tts">green book</annotation> + <annotation cp="📘">blue | book</annotation> + <annotation cp="📘" type="tts">blue book</annotation> + <annotation cp="📙">book | orange</annotation> + <annotation cp="📙" type="tts">orange book</annotation> + <annotation cp="📚">book | books</annotation> + <annotation cp="📚" type="tts">books</annotation> + <annotation cp="📓">notebook</annotation> + <annotation cp="📓" type="tts">notebook</annotation> + <annotation cp="📒">ledger | notebook</annotation> + <annotation cp="📒" type="tts">ledger</annotation> + <annotation cp="📃">curl | document | page | page with curl</annotation> + <annotation cp="📃" type="tts">page with curl</annotation> + <annotation cp="📜">paper | scroll</annotation> + <annotation cp="📜" type="tts">scroll</annotation> + <annotation cp="📄">document | page | page facing up</annotation> + <annotation cp="📄" type="tts">page facing up</annotation> + <annotation cp="📰">news | newspaper | paper</annotation> + <annotation cp="📰" type="tts">newspaper</annotation> + <annotation cp="🗞">news | newspaper | paper | rolled | rolled-up newspaper</annotation> + <annotation cp="🗞" type="tts">rolled-up newspaper</annotation> + <annotation cp="📑">bookmark | mark | marker | tabs</annotation> + <annotation cp="📑" type="tts">bookmark tabs</annotation> + <annotation cp="🔖">bookmark | mark</annotation> + <annotation cp="🔖" type="tts">bookmark</annotation> + <annotation cp="🏷">label</annotation> + <annotation cp="🏷" type="tts">label</annotation> + <annotation cp="💰">bag | dollar | money | moneybag</annotation> + <annotation cp="💰" type="tts">money bag</annotation> + <annotation cp="🪙">coin | gold | metal | money | silver | treasure</annotation> + <annotation cp="🪙" type="tts">coin</annotation> + <annotation cp="💴">banknote | bill | currency | money | note | yen</annotation> + <annotation cp="💴" type="tts">yen banknote</annotation> + <annotation cp="💵">banknote | bill | currency | dollar | money | note</annotation> + <annotation cp="💵" type="tts">dollar banknote</annotation> + <annotation cp="💶">banknote | bill | currency | euro | money | note</annotation> + <annotation cp="💶" type="tts">euro banknote</annotation> + <annotation cp="💷">banknote | bill | currency | money | note | pound</annotation> + <annotation cp="💷" type="tts">pound banknote</annotation> + <annotation cp="💸">banknote | bill | fly | money | money with wings | wings</annotation> + <annotation cp="💸" type="tts">money with wings</annotation> + <annotation cp="💳">card | credit | money</annotation> + <annotation cp="💳" type="tts">credit card</annotation> + <annotation cp="🧾">accounting | bookkeeping | evidence | proof | receipt</annotation> + <annotation cp="🧾" type="tts">receipt</annotation> + <annotation cp="💹">chart | chart increasing with yen | graph | growth | money | yen</annotation> + <annotation cp="💹" type="tts">chart increasing with yen</annotation> + <annotation cp="✉">email | envelope | letter</annotation> + <annotation cp="✉" type="tts">envelope</annotation> + <annotation cp="📧">e-mail | email | letter | mail</annotation> + <annotation cp="📧" type="tts">e-mail</annotation> + <annotation cp="📨">e-mail | email | envelope | incoming | letter | receive</annotation> + <annotation cp="📨" type="tts">incoming envelope</annotation> + <annotation cp="📩">arrow | e-mail | email | envelope | envelope with arrow | outgoing</annotation> + <annotation cp="📩" type="tts">envelope with arrow</annotation> + <annotation cp="📤">box | letter | mail | outbox | sent | tray</annotation> + <annotation cp="📤" type="tts">outbox tray</annotation> + <annotation cp="📥">box | inbox | letter | mail | receive | tray</annotation> + <annotation cp="📥" type="tts">inbox tray</annotation> + <annotation cp="📦">box | package | parcel</annotation> + <annotation cp="📦" type="tts">package</annotation> + <annotation cp="📫">closed | closed mailbox with raised flag | mail | mailbox | postbox</annotation> + <annotation cp="📫" type="tts">closed mailbox with raised flag</annotation> + <annotation cp="📪">closed | closed mailbox with lowered flag | lowered | mail | mailbox | postbox</annotation> + <annotation cp="📪" type="tts">closed mailbox with lowered flag</annotation> + <annotation cp="📬">mail | mailbox | open | open mailbox with raised flag | postbox</annotation> + <annotation cp="📬" type="tts">open mailbox with raised flag</annotation> + <annotation cp="📭">lowered | mail | mailbox | open | open mailbox with lowered flag | postbox</annotation> + <annotation cp="📭" type="tts">open mailbox with lowered flag</annotation> + <annotation cp="📮">mail | mailbox | postbox</annotation> + <annotation cp="📮" type="tts">postbox</annotation> + <annotation cp="🗳">ballot | ballot box with ballot | box</annotation> + <annotation cp="🗳" type="tts">ballot box with ballot</annotation> + <annotation cp="✏">pencil</annotation> + <annotation cp="✏" type="tts">pencil</annotation> + <annotation cp="✒">black nib | nib | pen</annotation> + <annotation cp="✒" type="tts">black nib</annotation> + <annotation cp="🖋">fountain | pen</annotation> + <annotation cp="🖋" type="tts">fountain pen</annotation> + <annotation cp="🖊">ballpoint | pen</annotation> + <annotation cp="🖊" type="tts">pen</annotation> + <annotation cp="🖌">paintbrush | painting</annotation> + <annotation cp="🖌" type="tts">paintbrush</annotation> + <annotation cp="🖍">crayon</annotation> + <annotation cp="🖍" type="tts">crayon</annotation> + <annotation cp="📝">memo | pencil</annotation> + <annotation cp="📝" type="tts">memo</annotation> + <annotation cp="💼">briefcase</annotation> + <annotation cp="💼" type="tts">briefcase</annotation> + <annotation cp="📁">file | folder</annotation> + <annotation cp="📁" type="tts">file folder</annotation> + <annotation cp="📂">file | folder | open</annotation> + <annotation cp="📂" type="tts">open file folder</annotation> + <annotation cp="🗂">card | dividers | index</annotation> + <annotation cp="🗂" type="tts">card index dividers</annotation> + <annotation cp="📅">calendar | date</annotation> + <annotation cp="📅" type="tts">calendar</annotation> + <annotation cp="📆">calendar | tear-off calendar</annotation> + <annotation cp="📆" type="tts">tear-off calendar</annotation> + <annotation cp="🗒">note | pad | spiral | spiral notepad</annotation> + <annotation cp="🗒" type="tts">spiral notepad</annotation> + <annotation cp="🗓">calendar | pad | spiral</annotation> + <annotation cp="🗓" type="tts">spiral calendar</annotation> + <annotation cp="📇">card | index | rolodex</annotation> + <annotation cp="📇" type="tts">card index</annotation> + <annotation cp="📈">chart | chart increasing | graph | growth | trend | upward</annotation> + <annotation cp="📈" type="tts">chart increasing</annotation> + <annotation cp="📉">chart | chart decreasing | down | graph | trend</annotation> + <annotation cp="📉" type="tts">chart decreasing</annotation> + <annotation cp="📊">bar | chart | graph</annotation> + <annotation cp="📊" type="tts">bar chart</annotation> + <annotation cp="📋">clipboard</annotation> + <annotation cp="📋" type="tts">clipboard</annotation> + <annotation cp="📌">pin | pushpin</annotation> + <annotation cp="📌" type="tts">pushpin</annotation> + <annotation cp="📍">pin | pushpin | round pushpin</annotation> + <annotation cp="📍" type="tts">round pushpin</annotation> + <annotation cp="📎">paperclip</annotation> + <annotation cp="📎" type="tts">paperclip</annotation> + <annotation cp="🖇">link | linked paperclips | paperclip</annotation> + <annotation cp="🖇" type="tts">linked paperclips</annotation> + <annotation cp="📏">ruler | straight edge | straight ruler</annotation> + <annotation cp="📏" type="tts">straight ruler</annotation> + <annotation cp="📐">ruler | set | triangle | triangular ruler</annotation> + <annotation cp="📐" type="tts">triangular ruler</annotation> + <annotation cp="✂">cutting | scissors | tool</annotation> + <annotation cp="✂" type="tts">scissors</annotation> + <annotation cp="🗃">box | card | file</annotation> + <annotation cp="🗃" type="tts">card file box</annotation> + <annotation cp="🗄">cabinet | file | filing</annotation> + <annotation cp="🗄" type="tts">file cabinet</annotation> + <annotation cp="🗑">wastebasket</annotation> + <annotation cp="🗑" type="tts">wastebasket</annotation> + <annotation cp="🔒">closed | locked</annotation> + <annotation cp="🔒" type="tts">locked</annotation> + <annotation cp="🔓">lock | open | unlock | unlocked</annotation> + <annotation cp="🔓" type="tts">unlocked</annotation> + <annotation cp="🔏">ink | lock | locked with pen | nib | pen | privacy</annotation> + <annotation cp="🔏" type="tts">locked with pen</annotation> + <annotation cp="🔐">closed | key | lock | locked with key | secure</annotation> + <annotation cp="🔐" type="tts">locked with key</annotation> + <annotation cp="🔑">key | lock | password</annotation> + <annotation cp="🔑" type="tts">key</annotation> + <annotation cp="🗝">clue | key | lock | old</annotation> + <annotation cp="🗝" type="tts">old key</annotation> + <annotation cp="🔨">hammer | tool</annotation> + <annotation cp="🔨" type="tts">hammer</annotation> + <annotation cp="🪓">axe | chop | hatchet | split | wood</annotation> + <annotation cp="🪓" type="tts">axe</annotation> + <annotation cp="⛏">mining | pick | tool</annotation> + <annotation cp="⛏" type="tts">pick</annotation> + <annotation cp="⚒">hammer | hammer and pick | pick | tool</annotation> + <annotation cp="⚒" type="tts">hammer and pick</annotation> + <annotation cp="🛠">hammer | hammer and wrench | spanner | tool | wrench</annotation> + <annotation cp="🛠" type="tts">hammer and wrench</annotation> + <annotation cp="🗡">dagger | knife | weapon</annotation> + <annotation cp="🗡" type="tts">dagger</annotation> + <annotation cp="⚔">crossed | swords | weapon</annotation> + <annotation cp="⚔" type="tts">crossed swords</annotation> + <annotation cp="🔫">gun | handgun | pistol | revolver | tool | water | weapon</annotation> + <annotation cp="🔫" type="tts">water pistol</annotation> + <annotation cp="🪃">australia | boomerang | rebound | repercussion</annotation> + <annotation cp="🪃" type="tts">boomerang</annotation> + <annotation cp="🏹">archer | arrow | bow | bow and arrow | Sagittarius | zodiac</annotation> + <annotation cp="🏹" type="tts">bow and arrow</annotation> + <annotation cp="🛡">shield | weapon</annotation> + <annotation cp="🛡" type="tts">shield</annotation> + <annotation cp="🪚">carpenter | carpentry saw | lumber | saw | tool</annotation> + <annotation cp="🪚" type="tts">carpentry saw</annotation> + <annotation cp="🔧">spanner | tool | wrench</annotation> + <annotation cp="🔧" type="tts">wrench</annotation> + <annotation cp="🪛">screw | screwdriver | tool</annotation> + <annotation cp="🪛" type="tts">screwdriver</annotation> + <annotation cp="🔩">bolt | nut | nut and bolt | tool</annotation> + <annotation cp="🔩" type="tts">nut and bolt</annotation> + <annotation cp="⚙">cog | cogwheel | gear | tool</annotation> + <annotation cp="⚙" type="tts">gear</annotation> + <annotation cp="🗜">clamp | compress | tool | vice</annotation> + <annotation cp="🗜" type="tts">clamp</annotation> + <annotation cp="⚖">balance | justice | Libra | scale | zodiac</annotation> + <annotation cp="⚖" type="tts">balance scale</annotation> + <annotation cp="🦯">accessibility | blind | white cane</annotation> + <annotation cp="🦯" type="tts">white cane</annotation> + <annotation cp="🔗">link</annotation> + <annotation cp="🔗" type="tts">link</annotation> + <annotation cp="⛓">chain | chains</annotation> + <annotation cp="⛓" type="tts">chains</annotation> + <annotation cp="🪝">catch | crook | curve | ensnare | hook | selling point</annotation> + <annotation cp="🪝" type="tts">hook</annotation> + <annotation cp="🧰">chest | mechanic | tool | toolbox</annotation> + <annotation cp="🧰" type="tts">toolbox</annotation> + <annotation cp="🧲">attraction | horseshoe | magnet | magnetic</annotation> + <annotation cp="🧲" type="tts">magnet</annotation> + <annotation cp="🪜">climb | ladder | rung | step</annotation> + <annotation cp="🪜" type="tts">ladder</annotation> + <annotation cp="⚗">alembic | chemistry | tool</annotation> + <annotation cp="⚗" type="tts">alembic</annotation> + <annotation cp="🧪">chemist | chemistry | experiment | lab | science | test tube</annotation> + <annotation cp="🧪" type="tts">test tube</annotation> + <annotation cp="🧫">bacteria | biologist | biology | culture | lab | petri dish</annotation> + <annotation cp="🧫" type="tts">petri dish</annotation> + <annotation cp="🧬">biologist | dna | evolution | gene | genetics | life</annotation> + <annotation cp="🧬" type="tts">dna</annotation> + <annotation cp="🔬">microscope | science | tool</annotation> + <annotation cp="🔬" type="tts">microscope</annotation> + <annotation cp="🔭">science | telescope | tool</annotation> + <annotation cp="🔭" type="tts">telescope</annotation> + <annotation cp="📡">antenna | dish | satellite</annotation> + <annotation cp="📡" type="tts">satellite antenna</annotation> + <annotation cp="💉">medicine | needle | shot | sick | syringe</annotation> + <annotation cp="💉" type="tts">syringe</annotation> + <annotation cp="🩸">bleed | blood donation | drop of blood | injury | medicine | menstruation</annotation> + <annotation cp="🩸" type="tts">drop of blood</annotation> + <annotation cp="💊">doctor | medicine | pill | sick</annotation> + <annotation cp="💊" type="tts">pill</annotation> + <annotation cp="🩹">adhesive bandage | bandage</annotation> + <annotation cp="🩹" type="tts">adhesive bandage</annotation> + <annotation cp="🩺">doctor | heart | medicine | stethoscope</annotation> + <annotation cp="🩺" type="tts">stethoscope</annotation> + <annotation cp="🚪">door</annotation> + <annotation cp="🚪" type="tts">door</annotation> + <annotation cp="🛗">accessibility | elevator | hoist | lift</annotation> + <annotation cp="🛗" type="tts">elevator</annotation> + <annotation cp="🪞">mirror | reflection | reflector | speculum</annotation> + <annotation cp="🪞" type="tts">mirror</annotation> + <annotation cp="🪟">frame | fresh air | opening | transparent | view | window</annotation> + <annotation cp="🪟" type="tts">window</annotation> + <annotation cp="🛏">bed | hotel | sleep</annotation> + <annotation cp="🛏" type="tts">bed</annotation> + <annotation cp="🛋">couch | couch and lamp | hotel | lamp</annotation> + <annotation cp="🛋" type="tts">couch and lamp</annotation> + <annotation cp="🪑">chair | seat | sit</annotation> + <annotation cp="🪑" type="tts">chair</annotation> + <annotation cp="🚽">toilet</annotation> + <annotation cp="🚽" type="tts">toilet</annotation> + <annotation cp="🪠">force cup | plumber | plunger | suction | toilet</annotation> + <annotation cp="🪠" type="tts">plunger</annotation> + <annotation cp="🚿">shower | water</annotation> + <annotation cp="🚿" type="tts">shower</annotation> + <annotation cp="🛁">bath | bathtub</annotation> + <annotation cp="🛁" type="tts">bathtub</annotation> + <annotation cp="🪤">bait | mouse trap | mousetrap | snare | trap</annotation> + <annotation cp="🪤" type="tts">mouse trap</annotation> + <annotation cp="🪒">razor | sharp | shave</annotation> + <annotation cp="🪒" type="tts">razor</annotation> + <annotation cp="🧴">lotion | lotion bottle | moisturizer | shampoo | sunscreen</annotation> + <annotation cp="🧴" type="tts">lotion bottle</annotation> + <annotation cp="🧷">diaper | punk rock | safety pin</annotation> + <annotation cp="🧷" type="tts">safety pin</annotation> + <annotation cp="🧹">broom | cleaning | sweeping | witch</annotation> + <annotation cp="🧹" type="tts">broom</annotation> + <annotation cp="🧺">basket | farming | laundry | picnic</annotation> + <annotation cp="🧺" type="tts">basket</annotation> + <annotation cp="🧻">paper towels | roll of paper | toilet paper</annotation> + <annotation cp="🧻" type="tts">roll of paper</annotation> + <annotation cp="🪣">bucket | cask | pail | vat</annotation> + <annotation cp="🪣" type="tts">bucket</annotation> + <annotation cp="🧼">bar | bathing | cleaning | lather | soap | soapdish</annotation> + <annotation cp="🧼" type="tts">soap</annotation> + <annotation cp="🪥">bathroom | brush | clean | dental | hygiene | teeth | toothbrush</annotation> + <annotation cp="🪥" type="tts">toothbrush</annotation> + <annotation cp="🧽">absorbing | cleaning | porous | sponge</annotation> + <annotation cp="🧽" type="tts">sponge</annotation> + <annotation cp="🧯">extinguish | fire | fire extinguisher | quench</annotation> + <annotation cp="🧯" type="tts">fire extinguisher</annotation> + <annotation cp="🛒">cart | shopping | trolley</annotation> + <annotation cp="🛒" type="tts">shopping cart</annotation> + <annotation cp="🚬">cigarette | smoking</annotation> + <annotation cp="🚬" type="tts">cigarette</annotation> + <annotation cp="⚰">coffin | death</annotation> + <annotation cp="⚰" type="tts">coffin</annotation> + <annotation cp="🪦">cemetery | grave | graveyard | headstone | tombstone</annotation> + <annotation cp="🪦" type="tts">headstone</annotation> + <annotation cp="⚱">ashes | death | funeral | urn</annotation> + <annotation cp="⚱" type="tts">funeral urn</annotation> + <annotation cp="🗿">face | moai | moyai | statue</annotation> + <annotation cp="🗿" type="tts">moai</annotation> + <annotation cp="🪧">demonstration | picket | placard | protest | sign</annotation> + <annotation cp="🪧" type="tts">placard</annotation> + <annotation cp="🏧">atm | ATM sign | automated | bank | teller</annotation> + <annotation cp="🏧" type="tts">ATM sign</annotation> + <annotation cp="🚮">litter | litter bin | litter in bin sign</annotation> + <annotation cp="🚮" type="tts">litter in bin sign</annotation> + <annotation cp="🚰">drinking | potable | water</annotation> + <annotation cp="🚰" type="tts">potable water</annotation> + <annotation cp="♿">access | wheelchair symbol</annotation> + <annotation cp="♿" type="tts">wheelchair symbol</annotation> + <annotation cp="🚹">lavatory | man | men’s room | restroom | wc</annotation> + <annotation cp="🚹" type="tts">men’s room</annotation> + <annotation cp="🚺">lavatory | restroom | wc | woman | women’s room</annotation> + <annotation cp="🚺" type="tts">women’s room</annotation> + <annotation cp="🚻">lavatory | restroom | WC</annotation> + <annotation cp="🚻" type="tts">restroom</annotation> + <annotation cp="🚼">baby | baby symbol | changing</annotation> + <annotation cp="🚼" type="tts">baby symbol</annotation> + <annotation cp="🚾">closet | lavatory | restroom | water | wc</annotation> + <annotation cp="🚾" type="tts">water closet</annotation> + <annotation cp="🛂">control | passport</annotation> + <annotation cp="🛂" type="tts">passport control</annotation> + <annotation cp="🛃">customs</annotation> + <annotation cp="🛃" type="tts">customs</annotation> + <annotation cp="🛄">baggage | claim</annotation> + <annotation cp="🛄" type="tts">baggage claim</annotation> + <annotation cp="🛅">baggage | left luggage | locker | luggage</annotation> + <annotation cp="🛅" type="tts">left luggage</annotation> + <annotation cp="⚠">warning</annotation> + <annotation cp="⚠" type="tts">warning</annotation> + <annotation cp="🚸">child | children crossing | crossing | pedestrian | traffic</annotation> + <annotation cp="🚸" type="tts">children crossing</annotation> + <annotation cp="⛔">entry | forbidden | no | not | prohibited | traffic</annotation> + <annotation cp="⛔" type="tts">no entry</annotation> + <annotation cp="🚫">entry | forbidden | no | not | prohibited</annotation> + <annotation cp="🚫" type="tts">prohibited</annotation> + <annotation cp="🚳">bicycle | bike | forbidden | no | no bicycles | prohibited</annotation> + <annotation cp="🚳" type="tts">no bicycles</annotation> + <annotation cp="🚭">forbidden | no | not | prohibited | smoking</annotation> + <annotation cp="🚭" type="tts">no smoking</annotation> + <annotation cp="🚯">forbidden | litter | no | no littering | not | prohibited</annotation> + <annotation cp="🚯" type="tts">no littering</annotation> + <annotation cp="🚱">non-drinking | non-potable | water</annotation> + <annotation cp="🚱" type="tts">non-potable water</annotation> + <annotation cp="🚷">forbidden | no | no pedestrians | not | pedestrian | prohibited</annotation> + <annotation cp="🚷" type="tts">no pedestrians</annotation> + <annotation cp="📵">cell | forbidden | mobile | no | no mobile phones | phone</annotation> + <annotation cp="📵" type="tts">no mobile phones</annotation> + <annotation cp="🔞">18 | age restriction | eighteen | no one under eighteen | prohibited | underage</annotation> + <annotation cp="🔞" type="tts">no one under eighteen</annotation> + <annotation cp="☢">radioactive | sign</annotation> + <annotation cp="☢" type="tts">radioactive</annotation> + <annotation cp="☣">biohazard | sign</annotation> + <annotation cp="☣" type="tts">biohazard</annotation> + <annotation cp="⬆">arrow | cardinal | direction | north | up arrow</annotation> + <annotation cp="⬆" type="tts">up arrow</annotation> + <annotation cp="↗">arrow | direction | intercardinal | northeast | up-right arrow</annotation> + <annotation cp="↗" type="tts">up-right arrow</annotation> + <annotation cp="➡">arrow | cardinal | direction | east | right arrow</annotation> + <annotation cp="➡" type="tts">right arrow</annotation> + <annotation cp="↘">arrow | direction | down-right arrow | intercardinal | southeast</annotation> + <annotation cp="↘" type="tts">down-right arrow</annotation> + <annotation cp="⬇">arrow | cardinal | direction | down | south</annotation> + <annotation cp="⬇" type="tts">down arrow</annotation> + <annotation cp="↙">arrow | direction | down-left arrow | intercardinal | southwest</annotation> + <annotation cp="↙" type="tts">down-left arrow</annotation> + <annotation cp="⬅">arrow | cardinal | direction | left arrow | west</annotation> + <annotation cp="⬅" type="tts">left arrow</annotation> + <annotation cp="↖">arrow | direction | intercardinal | northwest | up-left arrow</annotation> + <annotation cp="↖" type="tts">up-left arrow</annotation> + <annotation cp="↕">arrow | up-down arrow</annotation> + <annotation cp="↕" type="tts">up-down arrow</annotation> + <annotation cp="↔">arrow | left-right arrow</annotation> + <annotation cp="↔" type="tts">left-right arrow</annotation> + <annotation cp="↮">left right arrow stroke</annotation> + <annotation cp="↮" type="tts">left right arrow stroke</annotation> + <annotation cp="↩">arrow | right arrow curving left</annotation> + <annotation cp="↩" type="tts">right arrow curving left</annotation> + <annotation cp="↪">arrow | left arrow curving right</annotation> + <annotation cp="↪" type="tts">left arrow curving right</annotation> + <annotation cp="⤴">arrow | right arrow curving up</annotation> + <annotation cp="⤴" type="tts">right arrow curving up</annotation> + <annotation cp="⤵">arrow | down | right arrow curving down</annotation> + <annotation cp="⤵" type="tts">right arrow curving down</annotation> + <annotation cp="🔃">arrow | clockwise | clockwise vertical arrows | reload</annotation> + <annotation cp="🔃" type="tts">clockwise vertical arrows</annotation> + <annotation cp="🔄">anticlockwise | arrow | counterclockwise | counterclockwise arrows button | withershins</annotation> + <annotation cp="🔄" type="tts">counterclockwise arrows button</annotation> + <annotation cp="🔙">arrow | back | BACK arrow</annotation> + <annotation cp="🔙" type="tts">BACK arrow</annotation> + <annotation cp="🔚">arrow | end | END arrow</annotation> + <annotation cp="🔚" type="tts">END arrow</annotation> + <annotation cp="🔛">arrow | mark | on | ON! arrow</annotation> + <annotation cp="🔛" type="tts">ON! arrow</annotation> + <annotation cp="🔜">arrow | soon | SOON arrow</annotation> + <annotation cp="🔜" type="tts">SOON arrow</annotation> + <annotation cp="🔝">arrow | top | TOP arrow | up</annotation> + <annotation cp="🔝" type="tts">TOP arrow</annotation> + <annotation cp="🛐">place of worship | religion | worship</annotation> + <annotation cp="🛐" type="tts">place of worship</annotation> + <annotation cp="⚛">atheist | atom | atom symbol</annotation> + <annotation cp="⚛" type="tts">atom symbol</annotation> + <annotation cp="🕉">Hindu | om | religion</annotation> + <annotation cp="🕉" type="tts">om</annotation> + <annotation cp="✡">David | Jew | Jewish | religion | star | star of David</annotation> + <annotation cp="✡" type="tts">star of David</annotation> + <annotation cp="☸">Buddhist | dharma | religion | wheel | wheel of dharma</annotation> + <annotation cp="☸" type="tts">wheel of dharma</annotation> + <annotation cp="☯">religion | tao | taoist | yang | yin</annotation> + <annotation cp="☯" type="tts">yin yang</annotation> + <annotation cp="✝">Christian | cross | latin cross | religion</annotation> + <annotation cp="✝" type="tts">latin cross</annotation> + <annotation cp="☦">Christian | cross | orthodox cross | religion</annotation> + <annotation cp="☦" type="tts">orthodox cross</annotation> + <annotation cp="☪">islam | Muslim | religion | star and crescent</annotation> + <annotation cp="☪" type="tts">star and crescent</annotation> + <annotation cp="☮">peace | peace symbol</annotation> + <annotation cp="☮" type="tts">peace symbol</annotation> + <annotation cp="🕎">candelabrum | candlestick | menorah | religion</annotation> + <annotation cp="🕎" type="tts">menorah</annotation> + <annotation cp="🔯">dotted six-pointed star | fortune | star</annotation> + <annotation cp="🔯" type="tts">dotted six-pointed star</annotation> + <annotation cp="♈">Aries | ram | zodiac</annotation> + <annotation cp="♈" type="tts">Aries</annotation> + <annotation cp="♉">bull | ox | Taurus | zodiac</annotation> + <annotation cp="♉" type="tts">Taurus</annotation> + <annotation cp="♊">Gemini | twins | zodiac</annotation> + <annotation cp="♊" type="tts">Gemini</annotation> + <annotation cp="♋">Cancer | crab | zodiac</annotation> + <annotation cp="♋" type="tts">Cancer</annotation> + <annotation cp="♌">Leo | lion | zodiac</annotation> + <annotation cp="♌" type="tts">Leo</annotation> + <annotation cp="♍">Virgo | zodiac</annotation> + <annotation cp="♍" type="tts">Virgo</annotation> + <annotation cp="♎">balance | justice | Libra | scales | zodiac</annotation> + <annotation cp="♎" type="tts">Libra</annotation> + <annotation cp="♏">Scorpio | scorpion | scorpius | zodiac</annotation> + <annotation cp="♏" type="tts">Scorpio</annotation> + <annotation cp="♐">archer | Sagittarius | zodiac</annotation> + <annotation cp="♐" type="tts">Sagittarius</annotation> + <annotation cp="♑">Capricorn | goat | zodiac</annotation> + <annotation cp="♑" type="tts">Capricorn</annotation> + <annotation cp="♒">Aquarius | bearer | water | zodiac</annotation> + <annotation cp="♒" type="tts">Aquarius</annotation> + <annotation cp="♓">fish | Pisces | zodiac</annotation> + <annotation cp="♓" type="tts">Pisces</annotation> + <annotation cp="⛎">bearer | Ophiuchus | serpent | snake | zodiac</annotation> + <annotation cp="⛎" type="tts">Ophiuchus</annotation> + <annotation cp="🔀">arrow | crossed | shuffle tracks button</annotation> + <annotation cp="🔀" type="tts">shuffle tracks button</annotation> + <annotation cp="🔁">arrow | clockwise | repeat | repeat button</annotation> + <annotation cp="🔁" type="tts">repeat button</annotation> + <annotation cp="🔂">arrow | clockwise | once | repeat single button</annotation> + <annotation cp="🔂" type="tts">repeat single button</annotation> + <annotation cp="▶">arrow | play | play button | right | triangle</annotation> + <annotation cp="▶" type="tts">play button</annotation> + <annotation cp="⏩">arrow | double | fast | fast-forward button | forward</annotation> + <annotation cp="⏩" type="tts">fast-forward button</annotation> + <annotation cp="⏭">arrow | next scene | next track | next track button | triangle</annotation> + <annotation cp="⏭" type="tts">next track button</annotation> + <annotation cp="⏯">arrow | pause | play | play or pause button | right | triangle</annotation> + <annotation cp="⏯" type="tts">play or pause button</annotation> + <annotation cp="◀">arrow | left | reverse | reverse button | triangle</annotation> + <annotation cp="◀" type="tts">reverse button</annotation> + <annotation cp="⏪">arrow | double | fast reverse button | rewind</annotation> + <annotation cp="⏪" type="tts">fast reverse button</annotation> + <annotation cp="⏮">arrow | last track button | previous scene | previous track | triangle</annotation> + <annotation cp="⏮" type="tts">last track button</annotation> + <annotation cp="🔼">arrow | button | red | upwards button</annotation> + <annotation cp="🔼" type="tts">upwards button</annotation> + <annotation cp="⏫">arrow | double | fast up button</annotation> + <annotation cp="⏫" type="tts">fast up button</annotation> + <annotation cp="🔽">arrow | button | down | downwards button | red</annotation> + <annotation cp="🔽" type="tts">downwards button</annotation> + <annotation cp="⏬">arrow | double | down | fast down button</annotation> + <annotation cp="⏬" type="tts">fast down button</annotation> + <annotation cp="⏸">bar | double | pause | pause button | vertical</annotation> + <annotation cp="⏸" type="tts">pause button</annotation> + <annotation cp="⏹">square | stop | stop button</annotation> + <annotation cp="⏹" type="tts">stop button</annotation> + <annotation cp="⏺">circle | record | record button</annotation> + <annotation cp="⏺" type="tts">record button</annotation> + <annotation cp="⏏">eject | eject button</annotation> + <annotation cp="⏏" type="tts">eject button</annotation> + <annotation cp="🎦">camera | cinema | film | movie</annotation> + <annotation cp="🎦" type="tts">cinema</annotation> + <annotation cp="🔅">brightness | dim | dim button | low</annotation> + <annotation cp="🔅" type="tts">dim button</annotation> + <annotation cp="🔆">bright | bright button | brightness</annotation> + <annotation cp="🔆" type="tts">bright button</annotation> + <annotation cp="📶">antenna | antenna bars | bar | cell | mobile | phone</annotation> + <annotation cp="📶" type="tts">antenna bars</annotation> + <annotation cp="📳">cell | mobile | mode | phone | telephone | vibration</annotation> + <annotation cp="📳" type="tts">vibration mode</annotation> + <annotation cp="📴">cell | mobile | off | phone | telephone</annotation> + <annotation cp="📴" type="tts">mobile phone off</annotation> + <annotation cp="♀">female sign | woman</annotation> + <annotation cp="♀" type="tts">female sign</annotation> + <annotation cp="♂">male sign | man</annotation> + <annotation cp="♂" type="tts">male sign</annotation> + <annotation cp="⚧">transgender | transgender symbol</annotation> + <annotation cp="⚧" type="tts">transgender symbol</annotation> + <annotation cp="✖">× | cancel | multiplication | multiply | sign | x</annotation> + <annotation cp="✖" type="tts">multiply</annotation> + <annotation cp="➕">+ | math | plus | sign</annotation> + <annotation cp="➕" type="tts">plus</annotation> + <annotation cp="➖">- | − | math | minus | sign</annotation> + <annotation cp="➖" type="tts">minus</annotation> + <annotation cp="➗">÷ | divide | division | math | sign</annotation> + <annotation cp="➗" type="tts">divide</annotation> + <annotation cp="♾">forever | infinity | unbounded | universal</annotation> + <annotation cp="♾" type="tts">infinity</annotation> + <annotation cp="‼">! | !! | bangbang | double exclamation mark | exclamation | mark</annotation> + <annotation cp="‼" type="tts">double exclamation mark</annotation> + <annotation cp="⁉">! | !? | ? | exclamation | interrobang | mark | punctuation | question</annotation> + <annotation cp="⁉" type="tts">exclamation question mark</annotation> + <annotation cp="❓">? | mark | punctuation | question | red question mark</annotation> + <annotation cp="❓" type="tts">red question mark</annotation> + <annotation cp="❔">? | mark | outlined | punctuation | question | white question mark</annotation> + <annotation cp="❔" type="tts">white question mark</annotation> + <annotation cp="❕">! | exclamation | mark | outlined | punctuation | white exclamation mark</annotation> + <annotation cp="❕" type="tts">white exclamation mark</annotation> + <annotation cp="❗">! | exclamation | mark | punctuation | red exclamation mark</annotation> + <annotation cp="❗" type="tts">red exclamation mark</annotation> + <annotation cp="〰">dash | punctuation | wavy</annotation> + <annotation cp="〰" type="tts">wavy dash</annotation> + <annotation cp="💱">bank | currency | exchange | money</annotation> + <annotation cp="💱" type="tts">currency exchange</annotation> + <annotation cp="💲">currency | dollar | heavy dollar sign | money</annotation> + <annotation cp="💲" type="tts">heavy dollar sign</annotation> + <annotation cp="⚕">aesculapius | medical symbol | medicine | staff</annotation> + <annotation cp="⚕" type="tts">medical symbol</annotation> + <annotation cp="♻">recycle | recycling symbol</annotation> + <annotation cp="♻" type="tts">recycling symbol</annotation> + <annotation cp="⚜">fleur-de-lis</annotation> + <annotation cp="⚜" type="tts">fleur-de-lis</annotation> + <annotation cp="🔱">anchor | emblem | ship | tool | trident</annotation> + <annotation cp="🔱" type="tts">trident emblem</annotation> + <annotation cp="📛">badge | name</annotation> + <annotation cp="📛" type="tts">name badge</annotation> + <annotation cp="🔰">beginner | chevron | Japanese | Japanese symbol for beginner | leaf</annotation> + <annotation cp="🔰" type="tts">Japanese symbol for beginner</annotation> + <annotation cp="⭕">circle | hollow red circle | large | o | red</annotation> + <annotation cp="⭕" type="tts">hollow red circle</annotation> + <annotation cp="✅">✓ | button | check | mark</annotation> + <annotation cp="✅" type="tts">check mark button</annotation> + <annotation cp="☑">✓ | box | check | check box with check</annotation> + <annotation cp="☑" type="tts">check box with check</annotation> + <annotation cp="✔">✓ | check | mark</annotation> + <annotation cp="✔" type="tts">check mark</annotation> + <annotation cp="❌">× | cancel | cross | mark | multiplication | multiply | x</annotation> + <annotation cp="❌" type="tts">cross mark</annotation> + <annotation cp="❎">× | cross mark button | mark | square | x</annotation> + <annotation cp="❎" type="tts">cross mark button</annotation> + <annotation cp="➰">curl | curly loop | loop</annotation> + <annotation cp="➰" type="tts">curly loop</annotation> + <annotation cp="➿">curl | double | double curly loop | loop</annotation> + <annotation cp="➿" type="tts">double curly loop</annotation> + <annotation cp="〽">mark | part | part alternation mark</annotation> + <annotation cp="〽" type="tts">part alternation mark</annotation> + <annotation cp="✳">* | asterisk | eight-spoked asterisk</annotation> + <annotation cp="✳" type="tts">eight-spoked asterisk</annotation> + <annotation cp="✴">* | eight-pointed star | star</annotation> + <annotation cp="✴" type="tts">eight-pointed star</annotation> + <annotation cp="❇">* | sparkle</annotation> + <annotation cp="❇" type="tts">sparkle</annotation> + <annotation cp="©">c | copyright</annotation> + <annotation cp="©" type="tts">copyright</annotation> + <annotation cp="®">r | registered</annotation> + <annotation cp="®" type="tts">registered</annotation> + <annotation cp="™">mark | tm | trade mark | trademark</annotation> + <annotation cp="™" type="tts">trade mark</annotation> + <annotation cp="🔠">ABCD | input | latin | letters | uppercase</annotation> + <annotation cp="🔠" type="tts">input latin uppercase</annotation> + <annotation cp="🔡">abcd | input | latin | letters | lowercase</annotation> + <annotation cp="🔡" type="tts">input latin lowercase</annotation> + <annotation cp="🔢">1234 | input | numbers</annotation> + <annotation cp="🔢" type="tts">input numbers</annotation> + <annotation cp="🔣">〒♪&% | input | input symbols</annotation> + <annotation cp="🔣" type="tts">input symbols</annotation> + <annotation cp="🔤">abc | alphabet | input | latin | letters</annotation> + <annotation cp="🔤" type="tts">input latin letters</annotation> + <annotation cp="🅰">a | A button (blood type) | blood type</annotation> + <annotation cp="🅰" type="tts">A button (blood type)</annotation> + <annotation cp="🆎">ab | AB button (blood type) | blood type</annotation> + <annotation cp="🆎" type="tts">AB button (blood type)</annotation> + <annotation cp="🅱">b | B button (blood type) | blood type</annotation> + <annotation cp="🅱" type="tts">B button (blood type)</annotation> + <annotation cp="🆑">cl | CL button</annotation> + <annotation cp="🆑" type="tts">CL button</annotation> + <annotation cp="🆒">cool | COOL button</annotation> + <annotation cp="🆒" type="tts">COOL button</annotation> + <annotation cp="🆓">free | FREE button</annotation> + <annotation cp="🆓" type="tts">FREE button</annotation> + <annotation cp="ℹ">i | information</annotation> + <annotation cp="ℹ" type="tts">information</annotation> + <annotation cp="🆔">id | ID button | identity</annotation> + <annotation cp="🆔" type="tts">ID button</annotation> + <annotation cp="Ⓜ">circle | circled M | m</annotation> + <annotation cp="Ⓜ" type="tts">circled M</annotation> + <annotation cp="🆕">new | NEW button</annotation> + <annotation cp="🆕" type="tts">NEW button</annotation> + <annotation cp="🆖">ng | NG button</annotation> + <annotation cp="🆖" type="tts">NG button</annotation> + <annotation cp="🅾">blood type | o | O button (blood type)</annotation> + <annotation cp="🅾" type="tts">O button (blood type)</annotation> + <annotation cp="🆗">OK | OK button</annotation> + <annotation cp="🆗" type="tts">OK button</annotation> + <annotation cp="🅿">P button | parking</annotation> + <annotation cp="🅿" type="tts">P button</annotation> + <annotation cp="🆘">help | sos | SOS button</annotation> + <annotation cp="🆘" type="tts">SOS button</annotation> + <annotation cp="🆙">mark | up | UP! button</annotation> + <annotation cp="🆙" type="tts">UP! button</annotation> + <annotation cp="🆚">versus | vs | VS button</annotation> + <annotation cp="🆚" type="tts">VS button</annotation> + <annotation cp="🈁">“here” | Japanese | Japanese “here” button | katakana | ココ</annotation> + <annotation cp="🈁" type="tts">Japanese “here” button</annotation> + <annotation cp="🈂">“service charge” | Japanese | Japanese “service charge” button | katakana | サ</annotation> + <annotation cp="🈂" type="tts">Japanese “service charge” button</annotation> + <annotation cp="🈷">“monthly amount” | ideograph | Japanese | Japanese “monthly amount” button | 月</annotation> + <annotation cp="🈷" type="tts">Japanese “monthly amount” button</annotation> + <annotation cp="🈶">“not free of charge” | ideograph | Japanese | Japanese “not free of charge” button | 有</annotation> + <annotation cp="🈶" type="tts">Japanese “not free of charge” button</annotation> + <annotation cp="🈯">“reserved” | ideograph | Japanese | Japanese “reserved” button | 指</annotation> + <annotation cp="🈯" type="tts">Japanese “reserved” button</annotation> + <annotation cp="🉐">“bargain” | ideograph | Japanese | Japanese “bargain” button | 得</annotation> + <annotation cp="🉐" type="tts">Japanese “bargain” button</annotation> + <annotation cp="🈹">“discount” | ideograph | Japanese | Japanese “discount” button | 割</annotation> + <annotation cp="🈹" type="tts">Japanese “discount” button</annotation> + <annotation cp="🈚">“free of charge” | ideograph | Japanese | Japanese “free of charge” button | 無</annotation> + <annotation cp="🈚" type="tts">Japanese “free of charge” button</annotation> + <annotation cp="🈲">“prohibited” | ideograph | Japanese | Japanese “prohibited” button | 禁</annotation> + <annotation cp="🈲" type="tts">Japanese “prohibited” button</annotation> + <annotation cp="🉑">“acceptable” | ideograph | Japanese | Japanese “acceptable” button | 可</annotation> + <annotation cp="🉑" type="tts">Japanese “acceptable” button</annotation> + <annotation cp="🈸">“application” | ideograph | Japanese | Japanese “application” button | 申</annotation> + <annotation cp="🈸" type="tts">Japanese “application” button</annotation> + <annotation cp="🈴">“passing grade” | ideograph | Japanese | Japanese “passing grade” button | 合</annotation> + <annotation cp="🈴" type="tts">Japanese “passing grade” button</annotation> + <annotation cp="🈳">“vacancy” | ideograph | Japanese | Japanese “vacancy” button | 空</annotation> + <annotation cp="🈳" type="tts">Japanese “vacancy” button</annotation> + <annotation cp="㊗">“congratulations” | ideograph | Japanese | Japanese “congratulations” button | 祝</annotation> + <annotation cp="㊗" type="tts">Japanese “congratulations” button</annotation> + <annotation cp="㊙">“secret” | ideograph | Japanese | Japanese “secret” button | 秘</annotation> + <annotation cp="㊙" type="tts">Japanese “secret” button</annotation> + <annotation cp="🈺">“open for business” | ideograph | Japanese | Japanese “open for business” button | 営</annotation> + <annotation cp="🈺" type="tts">Japanese “open for business” button</annotation> + <annotation cp="🈵">“no vacancy” | ideograph | Japanese | Japanese “no vacancy” button | 満</annotation> + <annotation cp="🈵" type="tts">Japanese “no vacancy” button</annotation> + <annotation cp="🔴">circle | geometric | red</annotation> + <annotation cp="🔴" type="tts">red circle</annotation> + <annotation cp="🟠">circle | orange</annotation> + <annotation cp="🟠" type="tts">orange circle</annotation> + <annotation cp="🟡">circle | yellow</annotation> + <annotation cp="🟡" type="tts">yellow circle</annotation> + <annotation cp="🟢">circle | green</annotation> + <annotation cp="🟢" type="tts">green circle</annotation> + <annotation cp="🔵">blue | circle | geometric</annotation> + <annotation cp="🔵" type="tts">blue circle</annotation> + <annotation cp="🟣">circle | purple</annotation> + <annotation cp="🟣" type="tts">purple circle</annotation> + <annotation cp="🟤">brown | circle</annotation> + <annotation cp="🟤" type="tts">brown circle</annotation> + <annotation cp="⚫">black circle | circle | geometric</annotation> + <annotation cp="⚫" type="tts">black circle</annotation> + <annotation cp="⚪">circle | geometric | white circle</annotation> + <annotation cp="⚪" type="tts">white circle</annotation> + <annotation cp="🟥">red | square</annotation> + <annotation cp="🟥" type="tts">red square</annotation> + <annotation cp="🟧">orange | square</annotation> + <annotation cp="🟧" type="tts">orange square</annotation> + <annotation cp="🟨">square | yellow</annotation> + <annotation cp="🟨" type="tts">yellow square</annotation> + <annotation cp="🟩">green | square</annotation> + <annotation cp="🟩" type="tts">green square</annotation> + <annotation cp="🟦">blue | square</annotation> + <annotation cp="🟦" type="tts">blue square</annotation> + <annotation cp="🟪">purple | square</annotation> + <annotation cp="🟪" type="tts">purple square</annotation> + <annotation cp="🟫">brown | square</annotation> + <annotation cp="🟫" type="tts">brown square</annotation> + <annotation cp="⬛">black large square | geometric | square</annotation> + <annotation cp="⬛" type="tts">black large square</annotation> + <annotation cp="⬜">geometric | square | white large square</annotation> + <annotation cp="⬜" type="tts">white large square</annotation> + <annotation cp="◼">black medium square | geometric | square</annotation> + <annotation cp="◼" type="tts">black medium square</annotation> + <annotation cp="◻">geometric | square | white medium square</annotation> + <annotation cp="◻" type="tts">white medium square</annotation> + <annotation cp="◾">black medium-small square | geometric | square</annotation> + <annotation cp="◾" type="tts">black medium-small square</annotation> + <annotation cp="◽">geometric | square | white medium-small square</annotation> + <annotation cp="◽" type="tts">white medium-small square</annotation> + <annotation cp="▪">black small square | geometric | square</annotation> + <annotation cp="▪" type="tts">black small square</annotation> + <annotation cp="▫">geometric | square | white small square</annotation> + <annotation cp="▫" type="tts">white small square</annotation> + <annotation cp="🔶">diamond | geometric | large orange diamond | orange</annotation> + <annotation cp="🔶" type="tts">large orange diamond</annotation> + <annotation cp="🔷">blue | diamond | geometric | large blue diamond</annotation> + <annotation cp="🔷" type="tts">large blue diamond</annotation> + <annotation cp="🔸">diamond | geometric | orange | small orange diamond</annotation> + <annotation cp="🔸" type="tts">small orange diamond</annotation> + <annotation cp="🔹">blue | diamond | geometric | small blue diamond</annotation> + <annotation cp="🔹" type="tts">small blue diamond</annotation> + <annotation cp="🔺">geometric | red | red triangle pointed up</annotation> + <annotation cp="🔺" type="tts">red triangle pointed up</annotation> + <annotation cp="🔻">down | geometric | red | red triangle pointed down</annotation> + <annotation cp="🔻" type="tts">red triangle pointed down</annotation> + <annotation cp="💠">comic | diamond | diamond with a dot | geometric | inside</annotation> + <annotation cp="💠" type="tts">diamond with a dot</annotation> + <annotation cp="🔘">button | geometric | radio</annotation> + <annotation cp="🔘" type="tts">radio button</annotation> + <annotation cp="🔳">button | geometric | outlined | square | white square button</annotation> + <annotation cp="🔳" type="tts">white square button</annotation> + <annotation cp="🔲">black square button | button | geometric | square</annotation> + <annotation cp="🔲" type="tts">black square button</annotation> + <annotation cp="🏁">checkered | chequered | chequered flag | racing</annotation> + <annotation cp="🏁" type="tts">chequered flag</annotation> + <annotation cp="🚩">post | triangular flag</annotation> + <annotation cp="🚩" type="tts">triangular flag</annotation> + <annotation cp="🎌">celebration | cross | crossed | crossed flags | Japanese</annotation> + <annotation cp="🎌" type="tts">crossed flags</annotation> + <annotation cp="🏴">black flag | waving</annotation> + <annotation cp="🏴" type="tts">black flag</annotation> + <annotation cp="🏳">waving | white flag</annotation> + <annotation cp="🏳" type="tts">white flag</annotation> + <annotation cp="🏳🌈">pride | rainbow | rainbow flag</annotation> + <annotation cp="🏳🌈" type="tts">rainbow flag</annotation> + <annotation cp="🏳⚧">flag | light blue | pink | transgender | white</annotation> + <annotation cp="🏳⚧" type="tts">transgender flag</annotation> + <annotation cp="🏴☠">Jolly Roger | pirate | pirate flag | plunder | treasure</annotation> + <annotation cp="🏴☠" type="tts">pirate flag</annotation> + <annotation cp="¢">cent</annotation> + <annotation cp="¢" type="tts">cent</annotation> + <annotation cp="$">dollar | money | peso | USD</annotation> + <annotation cp="$" type="tts">dollar</annotation> + <annotation cp="£">currency | EGP | GBP | pound</annotation> + <annotation cp="£" type="tts">pound</annotation> + <annotation cp="¥">CNY | currency | JPY | yen | yuan</annotation> + <annotation cp="¥" type="tts">yen</annotation> + <annotation cp="₢">BRB | cruzeiro | currency</annotation> + <annotation cp="₢" type="tts">cruzeiro</annotation> + <annotation cp="₣">currency | franc | french franc</annotation> + <annotation cp="₣" type="tts">french franc</annotation> + <annotation cp="₤">currency | lira</annotation> + <annotation cp="₤" type="tts">lira</annotation> + <annotation cp="₥">mil | mill</annotation> + <annotation cp="₥" type="tts">mill</annotation> + <annotation cp="₩">KPW | KRW | won</annotation> + <annotation cp="₩" type="tts">won</annotation> + <annotation cp="€">currency | EUR | euro</annotation> + <annotation cp="€" type="tts">euro</annotation> + <annotation cp="₰">currency | german penny | pfennig</annotation> + <annotation cp="₰" type="tts">german penny</annotation> + <annotation cp="₱">peso</annotation> + <annotation cp="₱" type="tts">peso</annotation> + <annotation cp="₳">ARA | austral | currency</annotation> + <annotation cp="₳" type="tts">austral</annotation> + <annotation cp="₶">currency | livre tournois</annotation> + <annotation cp="₶" type="tts">livre tournois</annotation> + <annotation cp="₷">currency | spesmilo</annotation> + <annotation cp="₷" type="tts">spesmilo</annotation> + <annotation cp="₹">currency | indian rupee | rupee</annotation> + <annotation cp="₹" type="tts">indian rupee</annotation> + <annotation cp="₽">currency | ruble</annotation> + <annotation cp="₽" type="tts">ruble</annotation> + <annotation cp="₿">bitcoin | BTC</annotation> + <annotation cp="₿" type="tts">bitcoin</annotation> + <annotation cp="₨">currency | rupee</annotation> + <annotation cp="₨" type="tts">rupee</annotation> + <annotation cp="﷼">currency | rial</annotation> + <annotation cp="﷼" type="tts">rial</annotation> + <annotation cp="¹">one | superscript</annotation> + <annotation cp="¹" type="tts">superscript one</annotation> + <annotation cp="²">squared | superscript | two</annotation> + <annotation cp="²" type="tts">superscript two</annotation> + <annotation cp="³">cubed | superscript | three</annotation> + <annotation cp="³" type="tts">superscript three</annotation> + <annotation cp="µ">measure | micro sign</annotation> + <annotation cp="µ" type="tts">micro sign</annotation> + </annotations> +</ldml>
diff --git a/third_party/cldr/src/common/annotations/en_001.xml b/third_party/cldr/src/common/annotations/en_001.xml new file mode 100644 index 0000000..95da972 --- /dev/null +++ b/third_party/cldr/src/common/annotations/en_001.xml
@@ -0,0 +1,533 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE ldml SYSTEM "../../common/dtd/ldml.dtd"> +<!-- Copyright © 1991-2020 Unicode, Inc. +For terms of use, see http://www.unicode.org/copyright.html +Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/) + +Warnings: All cp values have U+FE0F characters removed. See /annotationsDerived/ for derived annotations. +--> +<ldml> + <identity> + <version number="$Revision$"/> + <language type="en"/> + <territory type="001"/> + </identity> + <annotations> + <annotation cp="_">dash | line | low dash | underdash | underline | underscore</annotation> + <annotation cp="_" type="tts">underscore</annotation> + <annotation cp="." type="tts">full stop</annotation> + <annotation cp="。">full stop | ideographic | ideographic full stop | period</annotation> + <annotation cp="。" type="tts">ideographic full stop</annotation> + <annotation cp="'">apostrophe | quote | single quote | straight apostrophe | typewriter apostrophe</annotation> + <annotation cp="'" type="tts">straight apostrophe</annotation> + <annotation cp="‘">apostrophe | curly quote | quote | single quote | smart quote</annotation> + <annotation cp="’">apostrophe | curly quote | quote | single quote | smart quote</annotation> + <annotation cp="“">curly quotes | double quote | quotation | quote | smart quotation</annotation> + <annotation cp="”">curly quotes | double quote | quotation | quote | smart quotation</annotation> + <annotation cp=")">bracket | parens | parenthesis | round bracket</annotation> + <annotation cp="【">bracket | lens bracket | lenticular bracket | open black lenticular bracket</annotation> + <annotation cp="【" type="tts">open black lenticular bracket</annotation> + <annotation cp="】">bracket | close black lenticular bracket | lens bracket | lenticular bracket</annotation> + <annotation cp="】" type="tts">close black lenticular bracket</annotation> + <annotation cp="〖">bracket | hollow lens bracket | hollow lenticular bracket | open hollow lenticular bracket</annotation> + <annotation cp="〖" type="tts">open hollow lenticular bracket</annotation> + <annotation cp="〗">bracket | close hollow lenticular bracket | hollow lens bracket | hollow lenticular bracket</annotation> + <annotation cp="〗" type="tts">close hollow lenticular bracket</annotation> + <annotation cp="@">ampersat | arobase | arroba | at mark | at sign | commercial at | strudel</annotation> + <annotation cp="@" type="tts">at sign</annotation> + <annotation cp="%" type="tts">per cent</annotation> + <annotation cp="‰">per mil | per mill | per mille | permil | permille</annotation> + <annotation cp="†">dagger | dagger sign | dagger symbol | obelisk | obelus</annotation> + <annotation cp="†" type="tts">dagger symbol</annotation> + <annotation cp="‡">dagger | double | double dagger sign | double dagger symbol | obelisk | obelus</annotation> + <annotation cp="‡" type="tts">double dagger symbol</annotation> + <annotation cp="^">accent | caret | chevron | circumflex | hat | power | wedge</annotation> + <annotation cp="↚" draft="contributed">leftwards arrow with stroke</annotation> + <annotation cp="↚" type="tts" draft="contributed">leftwards arrow with stroke</annotation> + <annotation cp="↛" draft="contributed">rightwards arrow with stroke</annotation> + <annotation cp="↛" type="tts" draft="contributed">rightwards arrow with stroke</annotation> + <annotation cp="↜" draft="contributed">arrow | left | wave arrow</annotation> + <annotation cp="↝" draft="contributed">arrow | right | wave arrow</annotation> + <annotation cp="↞" draft="contributed">leftwards two-headed arrow</annotation> + <annotation cp="↞" type="tts" draft="contributed">leftwards two-headed arrow</annotation> + <annotation cp="↟" draft="contributed">upwards two-headed arrow</annotation> + <annotation cp="↟" type="tts" draft="contributed">upwards two-headed arrow</annotation> + <annotation cp="↠" draft="contributed">rightwards two-headed arrow</annotation> + <annotation cp="↠" type="tts" draft="contributed">rightwards two-headed arrow</annotation> + <annotation cp="↡" draft="contributed">downwards two-headed arrow</annotation> + <annotation cp="↡" type="tts" draft="contributed">downwards two-headed arrow</annotation> + <annotation cp="↢">arrow | arrow with tail | left | leftwards arrow with tail</annotation> + <annotation cp="↢" type="tts">leftwards arrow with tail</annotation> + <annotation cp="↣" draft="contributed">rightwards arrow with tail</annotation> + <annotation cp="↣" type="tts" draft="contributed">rightwards arrow with tail</annotation> + <annotation cp="↤" draft="contributed">arrow | arrow from bar | left</annotation> + <annotation cp="↥" draft="contributed">arrow | arrow from bar | up</annotation> + <annotation cp="↦" draft="contributed">arrow | arrow from bar | right</annotation> + <annotation cp="↧" draft="contributed">arrow | arrow from bar | down</annotation> + <annotation cp="↨" draft="contributed">up-down arrow with base</annotation> + <annotation cp="↨" type="tts" draft="contributed">up-down arrow with base</annotation> + <annotation cp="↫" draft="contributed">leftwards arrow with loop</annotation> + <annotation cp="↫" type="tts" draft="contributed">leftwards arrow with loop</annotation> + <annotation cp="↬" draft="contributed">rightwards arrow with loop</annotation> + <annotation cp="↬" type="tts" draft="contributed">rightwards arrow with loop</annotation> + <annotation cp="↭" draft="contributed">left-right wave arrow</annotation> + <annotation cp="↭" type="tts" draft="contributed">left-right wave arrow</annotation> + <annotation cp="↰" draft="contributed">upwards arrow with leftwards tip</annotation> + <annotation cp="↰" type="tts" draft="contributed">upwards arrow with leftwards tip</annotation> + <annotation cp="↱" draft="contributed">upwards arrow with rightwards tip</annotation> + <annotation cp="↱" type="tts" draft="contributed">upwards arrow with rightwards tip</annotation> + <annotation cp="↲" draft="contributed">downwards arrow with leftwards tip</annotation> + <annotation cp="↲" type="tts" draft="contributed">downwards arrow with leftwards tip</annotation> + <annotation cp="↳" draft="contributed">downwards arrow with rightwards tip</annotation> + <annotation cp="↳" type="tts" draft="contributed">downwards arrow with rightwards tip</annotation> + <annotation cp="↴" draft="contributed">rightwards arrow with corner downwards</annotation> + <annotation cp="↴" type="tts" draft="contributed">rightwards arrow with corner downwards</annotation> + <annotation cp="↵" draft="contributed">downwards arrow with corner leftwards</annotation> + <annotation cp="↵" type="tts" draft="contributed">downwards arrow with corner leftwards</annotation> + <annotation cp="↶" draft="contributed">anticlockwise top-semicircle arrow</annotation> + <annotation cp="↶" type="tts" draft="contributed">anticlockwise top-semicircle arrow</annotation> + <annotation cp="↷" draft="contributed">clockwise top-semicircle arrow</annotation> + <annotation cp="↷" type="tts" draft="contributed">clockwise top-semicircle arrow</annotation> + <annotation cp="↸" draft="contributed">north-west arrow to long bar</annotation> + <annotation cp="↸" type="tts" draft="contributed">north-west arrow to long bar</annotation> + <annotation cp="↹" draft="contributed">arrow | arrow to bar | left | left arrow over right arrow | right</annotation> + <annotation cp="↺" draft="contributed">anticlockwise open-circle arrow</annotation> + <annotation cp="↺" type="tts" draft="contributed">anticlockwise open-circle arrow</annotation> + <annotation cp="↻" draft="contributed">clockwise open-circle arrow</annotation> + <annotation cp="↻" type="tts" draft="contributed">clockwise open-circle arrow</annotation> + <annotation cp="↼" draft="contributed">leftwards harpoon with upwards barb</annotation> + <annotation cp="↼" type="tts" draft="contributed">leftwards harpoon with upwards barb</annotation> + <annotation cp="↽" draft="contributed">leftwards harpoon with downwards barb</annotation> + <annotation cp="↽" type="tts" draft="contributed">leftwards harpoon with downwards barb</annotation> + <annotation cp="↾" draft="contributed">upwards harpoon with rightwards barb</annotation> + <annotation cp="↾" type="tts" draft="contributed">upwards harpoon with rightwards barb</annotation> + <annotation cp="↿" draft="contributed">upwards harpoon with leftwards barb</annotation> + <annotation cp="↿" type="tts" draft="contributed">upwards harpoon with leftwards barb</annotation> + <annotation cp="⇀" draft="contributed">rightwards harpoon with upwards barb</annotation> + <annotation cp="⇀" type="tts" draft="contributed">rightwards harpoon with upwards barb</annotation> + <annotation cp="⇁" draft="contributed">rightwards harpoon with downwards barb</annotation> + <annotation cp="⇁" type="tts" draft="contributed">rightwards harpoon with downwards barb</annotation> + <annotation cp="⇂" draft="contributed">downwards harpoon with rightwards barb</annotation> + <annotation cp="⇂" type="tts" draft="contributed">downwards harpoon with rightwards barb</annotation> + <annotation cp="⇃" draft="contributed">downwards harpoon with leftwards barb</annotation> + <annotation cp="⇃" type="tts" draft="contributed">downwards harpoon with leftwards barb</annotation> + <annotation cp="⇄" draft="contributed">arrow | left | right | rightwards arrow over leftwards arrow</annotation> + <annotation cp="⇇" draft="contributed">arrow | left | paired arrows</annotation> + <annotation cp="⇈" draft="contributed">arrow | paired arrows | up</annotation> + <annotation cp="⇉" draft="contributed">arrow | paired arrows | right</annotation> + <annotation cp="⇊" draft="contributed">arrow | down | paired arrows</annotation> + <annotation cp="⇋" draft="contributed">arrow | harpoon | left | leftwards harpoon over rightwards harpoon | right</annotation> + <annotation cp="⇌" draft="contributed">arrow | harpoon | left | right | rightwards harpoon over leftwards harpoon</annotation> + <annotation cp="⇐" draft="contributed">arrow | double arrow | left</annotation> + <annotation cp="⇍" draft="contributed">leftwards double arrow with stroke</annotation> + <annotation cp="⇍" type="tts" draft="contributed">leftwards double arrow with stroke</annotation> + <annotation cp="⇑" draft="contributed">arrow | double arrow | up</annotation> + <annotation cp="⇒" draft="contributed">arrow | double arrow | right</annotation> + <annotation cp="⇏" draft="contributed">rightwards double arrow with stroke</annotation> + <annotation cp="⇏" type="tts" draft="contributed">rightwards double arrow with stroke</annotation> + <annotation cp="⇓" draft="contributed">arrow | double arrow | down</annotation> + <annotation cp="⇔" draft="contributed">left-right double arrow</annotation> + <annotation cp="⇔" type="tts" draft="contributed">left-right double arrow</annotation> + <annotation cp="⇎" draft="contributed">left-right double arrow with stroke</annotation> + <annotation cp="⇎" type="tts" draft="contributed">left-right double arrow with stroke</annotation> + <annotation cp="⇖" draft="contributed">north-west double arrow</annotation> + <annotation cp="⇖" type="tts" draft="contributed">north-west double arrow</annotation> + <annotation cp="⇗" draft="contributed">north-east double arrow</annotation> + <annotation cp="⇗" type="tts" draft="contributed">north-east double arrow</annotation> + <annotation cp="⇘" draft="contributed">south-east double arrow</annotation> + <annotation cp="⇘" type="tts" draft="contributed">south-east double arrow</annotation> + <annotation cp="⇙" draft="contributed">south-west double arrow</annotation> + <annotation cp="⇙" type="tts" draft="contributed">south-west double arrow</annotation> + <annotation cp="⇚" draft="contributed">arrow | left | triple arrow</annotation> + <annotation cp="⇛" draft="contributed">arrow | right | triple arrow</annotation> + <annotation cp="⇜" draft="contributed">arrow | left | squiggle arrow</annotation> + <annotation cp="⇝" draft="contributed">arrow | right | squiggle arrow</annotation> + <annotation cp="⇞" draft="contributed">upwards arrow with double stroke</annotation> + <annotation cp="⇞" type="tts" draft="contributed">upwards arrow with double stroke</annotation> + <annotation cp="⇟" draft="contributed">downwards arrow with double stroke</annotation> + <annotation cp="⇟" type="tts" draft="contributed">downwards arrow with double stroke</annotation> + <annotation cp="⇠" draft="contributed">arrow | dashed arrow | left</annotation> + <annotation cp="⇡" draft="contributed">arrow | dashed arrow | up</annotation> + <annotation cp="⇢" draft="contributed">arrow | dashed arrow | right</annotation> + <annotation cp="⇣" draft="contributed">arrow | dashed arrow | down</annotation> + <annotation cp="⇤" draft="contributed">leftwards arrow to bar</annotation> + <annotation cp="⇤" type="tts" draft="contributed">leftwards arrow to bar</annotation> + <annotation cp="⇥" draft="contributed">rightwards arrow to bar</annotation> + <annotation cp="⇥" type="tts" draft="contributed">rightwards arrow to bar</annotation> + <annotation cp="⇦" draft="contributed">arrow | hollow arrow | left</annotation> + <annotation cp="⇧" draft="contributed">arrow | hollow arrow | up</annotation> + <annotation cp="⇨" draft="contributed">arrow | hollow arrow | right</annotation> + <annotation cp="⇩" draft="contributed">arrow | down | hollow arrow</annotation> + <annotation cp="⇪" draft="contributed">arrow | arrow from bar | hollow arrow | up</annotation> + <annotation cp="⇵" draft="contributed">downwards and upwards arrows</annotation> + <annotation cp="⇵" type="tts" draft="contributed">downwards and upwards arrows</annotation> + <annotation cp="|">bar | line | pipe | sheffer stroke | vertical bar</annotation> + <annotation cp="◐" draft="contributed">circle with left half filled</annotation> + <annotation cp="◐" type="tts" draft="contributed">circle with left half filled</annotation> + <annotation cp="◑" draft="contributed">circle with right half filled</annotation> + <annotation cp="◑" type="tts" draft="contributed">circle with right half filled</annotation> + <annotation cp="◒" draft="contributed">circle with lower half filled</annotation> + <annotation cp="◒" type="tts" draft="contributed">circle with lower half filled</annotation> + <annotation cp="◓" draft="contributed">circle with upper half filled</annotation> + <annotation cp="◓" type="tts" draft="contributed">circle with upper half filled</annotation> + <annotation cp="♪">eighth | music | note | quaver</annotation> + <annotation cp="♪" type="tts">quaver</annotation> + <annotation cp="😋">delicious | face | face savouring food | savouring | smile | yum</annotation> + <annotation cp="😋" type="tts">face savouring food</annotation> + <annotation cp="🤭">embarrassed | face with hand over mouth | oops | whoops</annotation> + <annotation cp="🤐">face | mouth | zip | zipper</annotation> + <annotation cp="🤨">distrust | face with raised eyebrow | sceptic | skeptic</annotation> + <annotation cp="😶🌫">absent-minded | face in clouds | face in the fog | head in clouds</annotation> + <annotation cp="😷">cold | doctor | face | face with medical mask | ill | mask | medicine | poorly | sick</annotation> + <annotation cp="🤒">face | face with thermometer | ill | poorly | sick | thermometer</annotation> + <annotation cp="🤕">bandage | face | face with head bandage | hurt | injury</annotation> + <annotation cp="🤕" type="tts">face with head bandage</annotation> + <annotation cp="🤧">bless you | face | gesundheit | sneeze</annotation> + <annotation cp="😵💫">dizzy | hypnotised | spiral | trouble | whoa</annotation> + <annotation cp="🤬">cursing | expletive | face with symbols on mouth | swearing</annotation> + <annotation cp="👻">creature | face | fairy tale | fantasy | ghost | monster | spectre</annotation> + <annotation cp="😼">cat | cat face with wry smile | face | ironic | smile | smirk | wry</annotation> + <annotation cp="💫">comic | dizzy | spinning | spinning stars | star</annotation> + <annotation cp="💬">balloon | bubble | comic | dialogue | speech</annotation> + <annotation cp="🗨">dialogue | speech</annotation> + <annotation cp="💭" type="tts">thought bubble</annotation> + <annotation cp="💤">comic | sleep | sleeping | sleepy | zzz</annotation> + <annotation cp="✌">hand | peace hand | peace sign | v | v sign | victory</annotation> + <annotation cp="🤘">finger | hand | horns | rock on</annotation> + <annotation cp="🤙">call | call-me hand | hand</annotation> + <annotation cp="🤙" type="tts">call-me hand</annotation> + <annotation cp="🙌">celebration | gesture | hand | hooray | raised | raising hands | woo hoo | yay</annotation> + <annotation cp="🫀">anatomical heart | cardiology | heart | organ | pulse</annotation> + <annotation cp="🧒">child | gender-neutral | toddler | young</annotation> + <annotation cp="🙇♂">apology | bowing | favour | gesture | man | sorry</annotation> + <annotation cp="🙇♀">apology | bowing | favour | gesture | sorry | woman</annotation> + <annotation cp="🧑🏫">instructor | lecturer | professor | teacher</annotation> + <annotation cp="🧑⚖">judge | law</annotation> + <annotation cp="👨🌾">farmer | gardener | man</annotation> + <annotation cp="👩🌾">farmer | gardener | woman</annotation> + <annotation cp="👨🔧">electrician | man | mechanic | plumber | tradesman | tradesperson</annotation> + <annotation cp="👩🔧">electrician | mechanic | plumber | tradesperson | tradeswoman | woman</annotation> + <annotation cp="🧑🚒">fire engine | fire truck | firefighter</annotation> + <annotation cp="👨🚒">fire engine | firefighter | fireman | man</annotation> + <annotation cp="👩🚒">fire engine | firefighter | firewoman | woman</annotation> + <annotation cp="👮♂">cop | man | officer | police | policeman</annotation> + <annotation cp="👮♀">cop | officer | police | policewoman | woman</annotation> + <annotation cp="💂♂">guard | guardsman | man</annotation> + <annotation cp="💂♀">guard | guardswoman | woman</annotation> + <annotation cp="👷">builder | construction | hat | worker</annotation> + <annotation cp="👷♂">builder | construction | man | worker</annotation> + <annotation cp="👷♀">builder | construction | woman | worker</annotation> + <annotation cp="👲">gua pi mao | hat | man | man with Chinese cap | skullcap</annotation> + <annotation cp="🤵">groom | person | person in tux | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵♂">man | man in tux | man in tuxedo | tuxedo</annotation> + <annotation cp="🤵♀">tuxedo | woman | woman in tux | woman in tuxedo</annotation> + <annotation cp="🤱">baby | breast | breastfeeding | nursing</annotation> + <annotation cp="🤱" type="tts">breastfeeding</annotation> + <annotation cp="🎅">celebration | Christmas | claus | father | Father Christmas | santa | Santa Claus</annotation> + <annotation cp="🤶">celebration | Christmas | claus | mother | Mrs | Mrs Claus</annotation> + <annotation cp="🤶" type="tts">Mrs Claus</annotation> + <annotation cp="💇">barber | beauty | haircut | hairdresser | parlour</annotation> + <annotation cp="🧑🦯">accessibility | blind | person with guide cane</annotation> + <annotation cp="🧑🦯" type="tts">person with guide cane</annotation> + <annotation cp="👨🦯">accessibility | blind | man | man with guide cane</annotation> + <annotation cp="👨🦯" type="tts">man with guide cane</annotation> + <annotation cp="👩🦯">accessibility | blind | woman | woman with guide cane</annotation> + <annotation cp="👩🦯" type="tts">woman with guide cane</annotation> + <annotation cp="🧑🦼">accessibility | person in powered wheelchair | wheelchair</annotation> + <annotation cp="🧑🦼" type="tts">person in powered wheelchair</annotation> + <annotation cp="👨🦼">accessibility | man | man in powered wheelchair | wheelchair</annotation> + <annotation cp="👨🦼" type="tts">man in powered wheelchair</annotation> + <annotation cp="👩🦼">accessibility | wheelchair | woman | woman in powered wheelchair</annotation> + <annotation cp="👩🦼" type="tts">woman in powered wheelchair</annotation> + <annotation cp="🚣">boat | rowboat | rowing boat</annotation> + <annotation cp="🚣♂">boat | man | rowboat | rowing boat</annotation> + <annotation cp="🚣♀">boat | rowboat | rowing boat | woman</annotation> + <annotation cp="🏋">person lifting weights | weight | weightlifter</annotation> + <annotation cp="🏋♂">man | weightlifter</annotation> + <annotation cp="🏋♀">weightlifter | woman</annotation> + <annotation cp="🚴">bicycle | biking | cyclist | person cycling</annotation> + <annotation cp="🚴♂">bicycle | biking | cyclist | man cycling</annotation> + <annotation cp="🚴♀">bicycle | biking | cyclist | woman cycling</annotation> + <annotation cp="🗣">face | head | silhouette | speak | speaking | talk | talking</annotation> + <annotation cp="🦳">grey | hair | old | white</annotation> + <annotation cp="🐽">face | nose | pig | snout</annotation> + <annotation cp="🐪">camel | dromedary | hump | one hump | single hump</annotation> + <annotation cp="🐫">bactrian | camel | hump | two humps | two-hump camel</annotation> + <annotation cp="🐓">bird | cockerel | rooster</annotation> + <annotation cp="🐓" type="tts">cockerel</annotation> + <annotation cp="🐊">alligator | croc | crocodile</annotation> + <annotation cp="🦖">T-Rex | T. rex | T. Rex | Tyrannosaurus Rex</annotation> + <annotation cp="🦭">sea lion | seal</annotation> + <annotation cp="🐞">beetle | insect | ladybeetle | ladybird | ladybug</annotation> + <annotation cp="🐞" type="tts">ladybird</annotation> + <annotation cp="🕸">spider | spider’s web | web</annotation> + <annotation cp="🕸" type="tts">spider’s web</annotation> + <annotation cp="🌼">blossom | daisy | flower</annotation> + <annotation cp="🪴">grow | house | nurturing | plant | potted plant</annotation> + <annotation cp="🌾">ear of rice | grain | rice | sheaf</annotation> + <annotation cp="🌾" type="tts">ear of rice</annotation> + <annotation cp="🍀" type="tts">four-leaf clover</annotation> + <annotation cp="🍆" type="tts">aubergine</annotation> + <annotation cp="🌶">chilli | hot | pepper</annotation> + <annotation cp="🌶" type="tts">chilli</annotation> + <annotation cp="🫑">bell pepper | capsicum | pepper | sweet pepper | vegetable</annotation> + <annotation cp="🫑" type="tts">pepper</annotation> + <annotation cp="🥬">bok choy | cabbage | kale | leafy green | lettuce | pak choi</annotation> + <annotation cp="🧄">flavouring | garlic</annotation> + <annotation cp="🧅">flavouring | onion</annotation> + <annotation cp="🥜">food | monkey nut | nut | nuts | peanut | peanuts</annotation> + <annotation cp="🥖">baguette | bread | food | french | french stick</annotation> + <annotation cp="🥖" type="tts">baguette</annotation> + <annotation cp="🫓">arepa | lavash | naan | nan | pita | pitta</annotation> + <annotation cp="🍔">beefburger | burger | hamburger</annotation> + <annotation cp="🍔" type="tts">beefburger</annotation> + <annotation cp="🍟">chips | french fries | fries</annotation> + <annotation cp="🍟" type="tts">chips</annotation> + <annotation cp="🥫">can | canned food | tin | tinned food</annotation> + <annotation cp="🥫" type="tts">tinned food</annotation> + <annotation cp="🍤" type="tts">fried prawn</annotation> + <annotation cp="🥡">oyster pail | takeaway box | takeout box</annotation> + <annotation cp="🦐">food | prawn | shellfish | shrimp | small</annotation> + <annotation cp="🦑">food | mollusc | squid</annotation> + <annotation cp="🍪">biscuit | cookie | dessert | sweet</annotation> + <annotation cp="🍪" type="tts">biscuit</annotation> + <annotation cp="🍰">cake | dessert | pastry | slice | sweet</annotation> + <annotation cp="🍰" type="tts">cake</annotation> + <annotation cp="🥧">filling | pastry | pie | slice | tart</annotation> + <annotation cp="🍬">dessert | sweet | sweets</annotation> + <annotation cp="🍬" type="tts">sweets</annotation> + <annotation cp="🍭">dessert | lollipop | lolly | sweet</annotation> + <annotation cp="🍮">crème caramel | custard | dessert | egg custard | pudding | sweet</annotation> + <annotation cp="🍮" type="tts">egg custard</annotation> + <annotation cp="🍼">baby | baby’s bottle | bottle | drink | milk</annotation> + <annotation cp="🍶">bar | beverage | bottle | cup | drink | saké</annotation> + <annotation cp="🍾">bar | bottle | bottle with popping cork | champagne | cork | drink | popping</annotation> + <annotation cp="🍺">bar | beer | drink | mug | stein</annotation> + <annotation cp="🍻">bar | beer | clink | clinking beer mugs | drink | mug | steins</annotation> + <annotation cp="🥤">cup with straw | fizzy drink | juice | soft drink</annotation> + <annotation cp="🧉">drink | maté</annotation> + <annotation cp="🧉" type="tts">maté</annotation> + <annotation cp="🍽">cooking | fork | knife | knife and fork with plate | plate</annotation> + <annotation cp="🍽" type="tts">knife and fork with plate</annotation> + <annotation cp="🍴">cooking | cutlery | fork | knife | knife and fork</annotation> + <annotation cp="🍴" type="tts">knife and fork</annotation> + <annotation cp="🥄">cutlery | spoon | tableware</annotation> + <annotation cp="🔪">cooking | cutlery | hocho | kitchen knife | knife | tool | weapon</annotation> + <annotation cp="🏺">amphora | Aquarius | drink | jar | jug | zodiac</annotation> + <annotation cp="🏕">camping | tent</annotation> + <annotation cp="🏖">beach | beach with umbrella | parasol | umbrella</annotation> + <annotation cp="🏚">derelict | dilapidated | house</annotation> + <annotation cp="🏪">convenience | shop | store</annotation> + <annotation cp="🎠">carousel | horse | merry-go-round</annotation> + <annotation cp="🎡">amusement park | ferris | theme park | wheel</annotation> + <annotation cp="🎢">amusement park | coaster | roller | rollercoaster | theme park</annotation> + <annotation cp="🎢" type="tts">rollercoaster</annotation> + <annotation cp="💈">barber | barber’s pole | haircut | pole</annotation> + <annotation cp="💈" type="tts">barber’s pole</annotation> + <annotation cp="🚃">car | electric | railway | railway carriage | train | tram | trolleybus</annotation> + <annotation cp="🚃" type="tts">railway carriage</annotation> + <annotation cp="🚄">high-speed train | railway | shinkansen | speed | TGV | train</annotation> + <annotation cp="🚇">metro | subway | tube | underground</annotation> + <annotation cp="🚕">cab | taxi | vehicle</annotation> + <annotation cp="🚖">cab | oncoming | taxi</annotation> + <annotation cp="🚗" type="tts">car</annotation> + <annotation cp="🚘" type="tts">oncoming car</annotation> + <annotation cp="🚙">4x4 | off-road vehicle | sport utility</annotation> + <annotation cp="🚚">delivery | delivery van | truck</annotation> + <annotation cp="🚚" type="tts">delivery van</annotation> + <annotation cp="🏎">car | motor racing | racing</annotation> + <annotation cp="🏍">motorbike | racing</annotation> + <annotation cp="🦼">accessibility | powered wheelchair</annotation> + <annotation cp="🦼" type="tts">powered wheelchair</annotation> + <annotation cp="🛺">auto rickshaw | tuk tuk | tuk-tuk</annotation> + <annotation cp="🚲">bicycle | bike | cycle</annotation> + <annotation cp="⛽">diesel | fuel | fuelpump | gas | petrol | pump | station</annotation> + <annotation cp="⛵">boat | resort | sailboat | sailing | sea | yacht</annotation> + <annotation cp="⛵" type="tts">sailing boat</annotation> + <annotation cp="🛳">cruise | liner | passenger | ship</annotation> + <annotation cp="✈" type="tts">aeroplane</annotation> + <annotation cp="🛩">aeroplane | airplane | small aeroplane | small airplane</annotation> + <annotation cp="🛩" type="tts">small aeroplane</annotation> + <annotation cp="🛫">aeroplane | airplane | check-in | departure | take-off</annotation> + <annotation cp="🛫" type="tts">aeroplane departure</annotation> + <annotation cp="🛬">aeroplane | aeroplane arrival | airplane | airplane arrival | arrivals | arriving | landing</annotation> + <annotation cp="🛬" type="tts">aeroplane arrival</annotation> + <annotation cp="🪂">hang-glide | parachute | parasail | parascend | skydive</annotation> + <annotation cp="🚁">chopper | helicopter | vehicle</annotation> + <annotation cp="🚠">cable | gondola | mountain | mountain cablecar</annotation> + <annotation cp="🕰">clock | mantel clock</annotation> + <annotation cp="🕧">12:30 | 12.30 | clock | half past twelve | twelve-thirty</annotation> + <annotation cp="🕧" type="tts">half past twelve</annotation> + <annotation cp="🕜">1:30 | 1.30 | clock | half past one | one-thirty</annotation> + <annotation cp="🕜" type="tts">half past one</annotation> + <annotation cp="🕝">2:30 | 2.30 | clock | half past two | two-thirty</annotation> + <annotation cp="🕝" type="tts">half past two</annotation> + <annotation cp="🕞">3:30 | 3.30 | clock | half past three | three-thirty</annotation> + <annotation cp="🕞" type="tts">half past three</annotation> + <annotation cp="🕟">4:30 | 4.30 | clock | four-thirty | half past four</annotation> + <annotation cp="🕟" type="tts">half past four</annotation> + <annotation cp="🕠">5:30 | 5.30 | clock | five-thirty | half past five</annotation> + <annotation cp="🕠" type="tts">half past five</annotation> + <annotation cp="🕡">6:30 | 6.30 | clock | half past six | six-thirty</annotation> + <annotation cp="🕡" type="tts">half past six</annotation> + <annotation cp="🕢">7:30 | 7.30 | clock | half past seven | seven-thirty</annotation> + <annotation cp="🕢" type="tts">half past seven</annotation> + <annotation cp="🕣">8:30 | 8.30 | clock | eight-thirty | half past eight</annotation> + <annotation cp="🕣" type="tts">half past eight</annotation> + <annotation cp="🕤">9:30 | 9.30 | clock | half past nine | nine-thirty</annotation> + <annotation cp="🕤" type="tts">half past nine</annotation> + <annotation cp="🕥">10:30 | 10.30 | clock | half past ten | ten-thirty</annotation> + <annotation cp="🕥" type="tts">half past ten</annotation> + <annotation cp="🕦">11:30 | 11.30 | clock | eleven-thirty | half past eleven</annotation> + <annotation cp="🕦" type="tts">half past eleven</annotation> + <annotation cp="🌓" draft="contributed">first-quarter moon</annotation> + <annotation cp="🌓" type="tts" draft="contributed">first-quarter moon</annotation> + <annotation cp="🌗" draft="contributed">last-quarter moon</annotation> + <annotation cp="🌗" type="tts" draft="contributed">last-quarter moon</annotation> + <annotation cp="🌛" draft="contributed">first-quarter moon face</annotation> + <annotation cp="🌛" type="tts" draft="contributed">first-quarter moon face</annotation> + <annotation cp="🌜" draft="contributed">last-quarter moon face</annotation> + <annotation cp="🌜" type="tts" draft="contributed">last-quarter moon face</annotation> + <annotation cp="⭐">star | yellow medium star</annotation> + <annotation cp="🌌">galaxy | milky way | space</annotation> + <annotation cp="🌪">cloud | tornado | twister | whirlwind</annotation> + <annotation cp="🌀">cyclone | dizzy | hurricane | typhoon</annotation> + <annotation cp="🌈">pride | rain | rainbow</annotation> + <annotation cp="💧">cold | drop | droplet | sweat</annotation> + <annotation cp="🌊">ocean | sea | swell | water | wave</annotation> + <annotation cp="🎃">celebration | halloween | jack | jack-o’-lantern | lantern | pumpkin</annotation> + <annotation cp="🎃" type="tts">jack-o’-lantern</annotation> + <annotation cp="🎋">banner | celebration | Japanese | star festival | tanabata | tree</annotation> + <annotation cp="🎏">carp | carp wind sock | celebration | Japanese wind socks | koinobori | streamer</annotation> + <annotation cp="🎑">celebration | ceremony | jugoya | moon | moon-viewing ceremony | otsukimi | tsukimi</annotation> + <annotation cp="🎗">awareness ribbon | celebration | reminder | ribbon</annotation> + <annotation cp="⚽" draft="contributed">football</annotation> + <annotation cp="⚽" type="tts" draft="contributed">football</annotation> + <annotation cp="🎾">ball | racket | racquet | tennis</annotation> + <annotation cp="🎳">ball | bowling | game | pins</annotation> + <annotation cp="🏏">ball | bat | cricket ball | cricket bat | cricket game | cricket match | game</annotation> + <annotation cp="🏏" type="tts">cricket match</annotation> + <annotation cp="🏑" draft="contributed">hockey</annotation> + <annotation cp="🏑" type="tts" draft="contributed">hockey</annotation> + <annotation cp="🏓" draft="contributed">table tennis</annotation> + <annotation cp="🏓" type="tts" draft="contributed">table tennis</annotation> + <annotation cp="🎣">angling | fish | fishing pole | pole</annotation> + <annotation cp="🤿" draft="contributed">diving | scuba | snorkelling</annotation> + <annotation cp="🛷" type="tts">sledge</annotation> + <annotation cp="🎯">bull | bullseye | dart | eye | game | hit | target</annotation> + <annotation cp="🪄">magic | wand | witch | wizard</annotation> + <annotation cp="🎰">fruit machine | game | one-armed bandit | slot | slot machine</annotation> + <annotation cp="🎲" type="tts">game dice</annotation> + <annotation cp="🪆">babushka | doll | matryoshka | nesting dolls | russia | Russian dolls</annotation> + <annotation cp="🪆" type="tts">Russian dolls</annotation> + <annotation cp="🎴">card | flower | flower playing cards | game | hanafuda | Japanese | playing</annotation> + <annotation cp="👓">clothing | eye | eyeglasses | eyewear | specs | spectacles</annotation> + <annotation cp="🦺">emergency | hi-vis | high-vis | jacket | life jacket | safety | vest</annotation> + <annotation cp="👔" type="tts">tie</annotation> + <annotation cp="👕">clothing | shirt | T-shirt | tshirt</annotation> + <annotation cp="👕" type="tts">T-shirt</annotation> + <annotation cp="👖">clothing | trousers</annotation> + <annotation cp="🩲" draft="contributed">bathing suit | briefs | one-piece | pants | swimsuit | underwear</annotation> + <annotation cp="🩳" draft="contributed">bathing suit | boardshorts | shorts | swim shorts | underwear</annotation> + <annotation cp="🎒" type="tts">school bag</annotation> + <annotation cp="🩴">beach sandals | flip-flop | flipflop | sandals | thong sandals | thongs | zori</annotation> + <annotation cp="🩴" type="tts">flip-flop</annotation> + <annotation cp="👟">athletic | clothing | shoe | trainer</annotation> + <annotation cp="👠">clothing | heel | shoe | stiletto | woman</annotation> + <annotation cp="🎓">cap | celebration | clothing | graduation | hat | mortarboard</annotation> + <annotation cp="📢">loud | loudhailer | public address</annotation> + <annotation cp="📢" type="tts">loudhailer</annotation> + <annotation cp="🎧">headphone | headphones</annotation> + <annotation cp="🎧" type="tts">headphones</annotation> + <annotation cp="🪘" type="tts">conga drum</annotation> + <annotation cp="📠">facsimile | fax | fax machine</annotation> + <annotation cp="🎥">camera | cinema | film | movie</annotation> + <annotation cp="🎥" type="tts">film camera</annotation> + <annotation cp="🎬">clapper | clapperboard | movie</annotation> + <annotation cp="🎬" type="tts">clapperboard</annotation> + <annotation cp="📺">tele | television | telly | tv | video</annotation> + <annotation cp="📹">camcorder | camera | video</annotation> + <annotation cp="🔦">electric | light | tool | torch</annotation> + <annotation cp="🔦" type="tts">torch</annotation> + <annotation cp="🏷">label | tag</annotation> + <annotation cp="💷">bank | banknote | bill | currency | money | note | pound | sterling</annotation> + <annotation cp="📧">email | letter | mail</annotation> + <annotation cp="📧" type="tts">email</annotation> + <annotation cp="📫">closed | closed mailbox with raised flag | closed postbox with raised flag | letterbox | mail | mailbox | post | post box | postbox</annotation> + <annotation cp="📪">closed | closed mailbox with lowered flag | closed postbox with lowered flag | letterbox | lowered | mail | mailbox | post | post box | postbox</annotation> + <annotation cp="📬">mail | mailbox | open | open mailbox with raised flag | open postbox with raised flag | post | post box | postbox</annotation> + <annotation cp="📭">lowered | mail | mailbox | open | open mailbox with lowered flag | open postbox with lowered flag | post | post box | postbox</annotation> + <annotation cp="📌">map pin | pin | pushpin</annotation> + <annotation cp="📐">ruler | set | set square | triangle</annotation> + <annotation cp="📐" type="tts">set square</annotation> + <annotation cp="🗑">paper bin | wastebasket | wastepaper basket</annotation> + <annotation cp="🗑" type="tts">wastepaper basket</annotation> + <annotation cp="⛏">mining | pick | pickaxe | tool</annotation> + <annotation cp="⚒">hammer | hammer and pick | hammer and pickaxe | pick | tool</annotation> + <annotation cp="🛠" draft="contributed">hammer and spanner</annotation> + <annotation cp="🛠" type="tts" draft="contributed">hammer and spanner</annotation> + <annotation cp="🔫" draft="contributed">water pistol</annotation> + <annotation cp="🪚">carpenter | lumber | saw | timber | tool</annotation> + <annotation cp="🔧" draft="contributed">spanner</annotation> + <annotation cp="🔧" type="tts" draft="contributed">spanner</annotation> + <annotation cp="🦯" draft="contributed">guide cane</annotation> + <annotation cp="🦯" type="tts" draft="contributed">guide cane</annotation> + <annotation cp="🪜">climb | ladder | rung | stair | step</annotation> + <annotation cp="💉">ill | injection | medicine | needle | syringe</annotation> + <annotation cp="💊">capsule | doctor | medicine | pill | sick | tablet</annotation> + <annotation cp="🩹" draft="contributed">plaster</annotation> + <annotation cp="🩹" type="tts" draft="contributed">plaster</annotation> + <annotation cp="🛗" type="tts">lift</annotation> + <annotation cp="🪞">looking glass | reflection | reflector</annotation> + <annotation cp="🛋">couch | hotel | lamp | sofa | sofa and lamp</annotation> + <annotation cp="🛋" type="tts">sofa and lamp</annotation> + <annotation cp="🚽">lavatory | loo | toilet</annotation> + <annotation cp="🛁">bath | bathtub | tub</annotation> + <annotation cp="🛁" type="tts">bath</annotation> + <annotation cp="🪤" type="tts">mousetrap</annotation> + <annotation cp="🪒" draft="contributed">razor</annotation> + <annotation cp="🧴">lotion | lotion bottle | moisturiser | shampoo | sunscreen</annotation> + <annotation cp="🧷">nappy | punk rock | safety pin</annotation> + <annotation cp="🧻">paper towels | roll of paper | toilet paper | toilet roll</annotation> + <annotation cp="🛒" draft="contributed">shopping trolley</annotation> + <annotation cp="🛒" type="tts" draft="contributed">shopping trolley</annotation> + <annotation cp="🏧">atm | ATM sign | automated | bank | cashpoint | teller</annotation> + <annotation cp="🚮">litter | litter bin | litter in bin sign | rubbish</annotation> + <annotation cp="🚰" draft="contributed">drinking water</annotation> + <annotation cp="🚰" type="tts" draft="contributed">drinking water</annotation> + <annotation cp="🚹">lavatory | man | men’s toilet | restroom | wc</annotation> + <annotation cp="🚹" type="tts">men’s toilet</annotation> + <annotation cp="🚺">lavatory | restroom | wc | woman | women’s toilet</annotation> + <annotation cp="🚺" type="tts">women’s toilet</annotation> + <annotation cp="🚻">lavatory | restroom | toilets | WC</annotation> + <annotation cp="🚻" type="tts">toilets</annotation> + <annotation cp="🚱" draft="contributed">non-drinking water</annotation> + <annotation cp="🚱" type="tts" draft="contributed">non-drinking water</annotation> + <annotation cp="↮" draft="contributed">left-right arrow with stroke</annotation> + <annotation cp="↮" type="tts" draft="contributed">left-right arrow with stroke</annotation> + <annotation cp="🔄" draft="contributed">anticlockwise arrows button</annotation> + <annotation cp="🔄" type="tts" draft="contributed">anticlockwise arrows button</annotation> + <annotation cp="🕉">aum | Hindu | om | religion</annotation> + <annotation cp="☸">Buddhist | dharma | dharmachakra | religion | wheel | wheel of dharma</annotation> + <annotation cp="⏪" draft="contributed">fast-reverse button</annotation> + <annotation cp="⏪" type="tts" draft="contributed">fast-reverse button</annotation> + <annotation cp="⚧">trans | transgender | transgender symbol</annotation> + <annotation cp="➕">+ | math | maths | plus | sign</annotation> + <annotation cp="➖">- | − | math | maths | minus | sign</annotation> + <annotation cp="➗">÷ | divide | division | heavy division sign | math | maths</annotation> + <annotation cp="✅">✓ | button | mark | tick</annotation> + <annotation cp="✅" type="tts">tick button</annotation> + <annotation cp="☑">✓ | box | tick | tick box with tick</annotation> + <annotation cp="☑" type="tts">tick box with tick</annotation> + <annotation cp="✔">✓ | mark | tick</annotation> + <annotation cp="✔" type="tts">tick</annotation> + <annotation cp="™">mark | tm | trademark</annotation> + <annotation cp="™" type="tts">trademark</annotation> + <annotation cp="🏳⚧">flag | light blue | pink | trans | transgender | white</annotation> + <annotation cp="£">currency | EGP | GBP | pound | quid | sterling</annotation> + <annotation cp="₽">currency | rouble | ruble</annotation> + <annotation cp="₽" type="tts">rouble</annotation> + <annotation cp="µ">measure | micro sign | mu</annotation> + </annotations> +</ldml>
diff --git a/third_party/cldr/src/common/annotationsDerived/en.xml b/third_party/cldr/src/common/annotationsDerived/en.xml new file mode 100644 index 0000000..f9cdaa9 --- /dev/null +++ b/third_party/cldr/src/common/annotationsDerived/en.xml
@@ -0,0 +1,4071 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE ldml SYSTEM "../../common/dtd/ldml.dtd"> +<!-- Copyright © 1991-2020 Unicode, Inc. +For terms of use, see http://www.unicode.org/copyright.html +Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/) + +Derived short names and annotations, using GenerateDerivedAnnotations.java. See warnings in /annotations/ file. +--> +<ldml> + <identity> + <version number="$Revision$"/> + <language type="en"/> + </identity> + <annotations> + <annotation cp="👋🏻">hand | light skin tone | wave | waving</annotation> + <annotation cp="👋🏻" type="tts">waving hand: light skin tone</annotation> + <annotation cp="👋🏼">hand | medium-light skin tone | wave | waving</annotation> + <annotation cp="👋🏼" type="tts">waving hand: medium-light skin tone</annotation> + <annotation cp="👋🏽">hand | medium skin tone | wave | waving</annotation> + <annotation cp="👋🏽" type="tts">waving hand: medium skin tone</annotation> + <annotation cp="👋🏾">hand | medium-dark skin tone | wave | waving</annotation> + <annotation cp="👋🏾" type="tts">waving hand: medium-dark skin tone</annotation> + <annotation cp="👋🏿">dark skin tone | hand | wave | waving</annotation> + <annotation cp="👋🏿" type="tts">waving hand: dark skin tone</annotation> + <annotation cp="🤚🏻">backhand | light skin tone | raised | raised back of hand</annotation> + <annotation cp="🤚🏻" type="tts">raised back of hand: light skin tone</annotation> + <annotation cp="🤚🏼">backhand | medium-light skin tone | raised | raised back of hand</annotation> + <annotation cp="🤚🏼" type="tts">raised back of hand: medium-light skin tone</annotation> + <annotation cp="🤚🏽">backhand | medium skin tone | raised | raised back of hand</annotation> + <annotation cp="🤚🏽" type="tts">raised back of hand: medium skin tone</annotation> + <annotation cp="🤚🏾">backhand | medium-dark skin tone | raised | raised back of hand</annotation> + <annotation cp="🤚🏾" type="tts">raised back of hand: medium-dark skin tone</annotation> + <annotation cp="🤚🏿">backhand | dark skin tone | raised | raised back of hand</annotation> + <annotation cp="🤚🏿" type="tts">raised back of hand: dark skin tone</annotation> + <annotation cp="🖐🏻">finger | hand | hand with fingers splayed | light skin tone | splayed</annotation> + <annotation cp="🖐🏻" type="tts">hand with fingers splayed: light skin tone</annotation> + <annotation cp="🖐🏼">finger | hand | hand with fingers splayed | medium-light skin tone | splayed</annotation> + <annotation cp="🖐🏼" type="tts">hand with fingers splayed: medium-light skin tone</annotation> + <annotation cp="🖐🏽">finger | hand | hand with fingers splayed | medium skin tone | splayed</annotation> + <annotation cp="🖐🏽" type="tts">hand with fingers splayed: medium skin tone</annotation> + <annotation cp="🖐🏾">finger | hand | hand with fingers splayed | medium-dark skin tone | splayed</annotation> + <annotation cp="🖐🏾" type="tts">hand with fingers splayed: medium-dark skin tone</annotation> + <annotation cp="🖐🏿">dark skin tone | finger | hand | hand with fingers splayed | splayed</annotation> + <annotation cp="🖐🏿" type="tts">hand with fingers splayed: dark skin tone</annotation> + <annotation cp="✋🏻">hand | high 5 | high five | light skin tone | raised hand</annotation> + <annotation cp="✋🏻" type="tts">raised hand: light skin tone</annotation> + <annotation cp="✋🏼">hand | high 5 | high five | medium-light skin tone | raised hand</annotation> + <annotation cp="✋🏼" type="tts">raised hand: medium-light skin tone</annotation> + <annotation cp="✋🏽">hand | high 5 | high five | medium skin tone | raised hand</annotation> + <annotation cp="✋🏽" type="tts">raised hand: medium skin tone</annotation> + <annotation cp="✋🏾">hand | high 5 | high five | medium-dark skin tone | raised hand</annotation> + <annotation cp="✋🏾" type="tts">raised hand: medium-dark skin tone</annotation> + <annotation cp="✋🏿">dark skin tone | hand | high 5 | high five | raised hand</annotation> + <annotation cp="✋🏿" type="tts">raised hand: dark skin tone</annotation> + <annotation cp="🖖🏻">finger | hand | light skin tone | spock | vulcan | vulcan salute</annotation> + <annotation cp="🖖🏻" type="tts">vulcan salute: light skin tone</annotation> + <annotation cp="🖖🏼">finger | hand | medium-light skin tone | spock | vulcan | vulcan salute</annotation> + <annotation cp="🖖🏼" type="tts">vulcan salute: medium-light skin tone</annotation> + <annotation cp="🖖🏽">finger | hand | medium skin tone | spock | vulcan | vulcan salute</annotation> + <annotation cp="🖖🏽" type="tts">vulcan salute: medium skin tone</annotation> + <annotation cp="🖖🏾">finger | hand | medium-dark skin tone | spock | vulcan | vulcan salute</annotation> + <annotation cp="🖖🏾" type="tts">vulcan salute: medium-dark skin tone</annotation> + <annotation cp="🖖🏿">dark skin tone | finger | hand | spock | vulcan | vulcan salute</annotation> + <annotation cp="🖖🏿" type="tts">vulcan salute: dark skin tone</annotation> + <annotation cp="👌🏻">hand | light skin tone | OK</annotation> + <annotation cp="👌🏻" type="tts">OK hand: light skin tone</annotation> + <annotation cp="👌🏼">hand | medium-light skin tone | OK</annotation> + <annotation cp="👌🏼" type="tts">OK hand: medium-light skin tone</annotation> + <annotation cp="👌🏽">hand | medium skin tone | OK</annotation> + <annotation cp="👌🏽" type="tts">OK hand: medium skin tone</annotation> + <annotation cp="👌🏾">hand | medium-dark skin tone | OK</annotation> + <annotation cp="👌🏾" type="tts">OK hand: medium-dark skin tone</annotation> + <annotation cp="👌🏿">dark skin tone | hand | OK</annotation> + <annotation cp="👌🏿" type="tts">OK hand: dark skin tone</annotation> + <annotation cp="🤌🏻">fingers | hand gesture | interrogation | light skin tone | pinched | sarcastic</annotation> + <annotation cp="🤌🏻" type="tts">pinched fingers: light skin tone</annotation> + <annotation cp="🤌🏼">fingers | hand gesture | interrogation | medium-light skin tone | pinched | sarcastic</annotation> + <annotation cp="🤌🏼" type="tts">pinched fingers: medium-light skin tone</annotation> + <annotation cp="🤌🏽">fingers | hand gesture | interrogation | medium skin tone | pinched | sarcastic</annotation> + <annotation cp="🤌🏽" type="tts">pinched fingers: medium skin tone</annotation> + <annotation cp="🤌🏾">fingers | hand gesture | interrogation | medium-dark skin tone | pinched | sarcastic</annotation> + <annotation cp="🤌🏾" type="tts">pinched fingers: medium-dark skin tone</annotation> + <annotation cp="🤌🏿">dark skin tone | fingers | hand gesture | interrogation | pinched | sarcastic</annotation> + <annotation cp="🤌🏿" type="tts">pinched fingers: dark skin tone</annotation> + <annotation cp="🤏🏻">light skin tone | pinching hand | small amount</annotation> + <annotation cp="🤏🏻" type="tts">pinching hand: light skin tone</annotation> + <annotation cp="🤏🏼">medium-light skin tone | pinching hand | small amount</annotation> + <annotation cp="🤏🏼" type="tts">pinching hand: medium-light skin tone</annotation> + <annotation cp="🤏🏽">medium skin tone | pinching hand | small amount</annotation> + <annotation cp="🤏🏽" type="tts">pinching hand: medium skin tone</annotation> + <annotation cp="🤏🏾">medium-dark skin tone | pinching hand | small amount</annotation> + <annotation cp="🤏🏾" type="tts">pinching hand: medium-dark skin tone</annotation> + <annotation cp="🤏🏿">dark skin tone | pinching hand | small amount</annotation> + <annotation cp="🤏🏿" type="tts">pinching hand: dark skin tone</annotation> + <annotation cp="✌🏻">hand | light skin tone | v | victory</annotation> + <annotation cp="✌🏻" type="tts">victory hand: light skin tone</annotation> + <annotation cp="✌🏼">hand | medium-light skin tone | v | victory</annotation> + <annotation cp="✌🏼" type="tts">victory hand: medium-light skin tone</annotation> + <annotation cp="✌🏽">hand | medium skin tone | v | victory</annotation> + <annotation cp="✌🏽" type="tts">victory hand: medium skin tone</annotation> + <annotation cp="✌🏾">hand | medium-dark skin tone | v | victory</annotation> + <annotation cp="✌🏾" type="tts">victory hand: medium-dark skin tone</annotation> + <annotation cp="✌🏿">dark skin tone | hand | v | victory</annotation> + <annotation cp="✌🏿" type="tts">victory hand: dark skin tone</annotation> + <annotation cp="🤞🏻">cross | crossed fingers | finger | hand | light skin tone | luck</annotation> + <annotation cp="🤞🏻" type="tts">crossed fingers: light skin tone</annotation> + <annotation cp="🤞🏼">cross | crossed fingers | finger | hand | luck | medium-light skin tone</annotation> + <annotation cp="🤞🏼" type="tts">crossed fingers: medium-light skin tone</annotation> + <annotation cp="🤞🏽">cross | crossed fingers | finger | hand | luck | medium skin tone</annotation> + <annotation cp="🤞🏽" type="tts">crossed fingers: medium skin tone</annotation> + <annotation cp="🤞🏾">cross | crossed fingers | finger | hand | luck | medium-dark skin tone</annotation> + <annotation cp="🤞🏾" type="tts">crossed fingers: medium-dark skin tone</annotation> + <annotation cp="🤞🏿">cross | crossed fingers | dark skin tone | finger | hand | luck</annotation> + <annotation cp="🤞🏿" type="tts">crossed fingers: dark skin tone</annotation> + <annotation cp="🤟🏻">hand | ILY | light skin tone | love-you gesture</annotation> + <annotation cp="🤟🏻" type="tts">love-you gesture: light skin tone</annotation> + <annotation cp="🤟🏼">hand | ILY | love-you gesture | medium-light skin tone</annotation> + <annotation cp="🤟🏼" type="tts">love-you gesture: medium-light skin tone</annotation> + <annotation cp="🤟🏽">hand | ILY | love-you gesture | medium skin tone</annotation> + <annotation cp="🤟🏽" type="tts">love-you gesture: medium skin tone</annotation> + <annotation cp="🤟🏾">hand | ILY | love-you gesture | medium-dark skin tone</annotation> + <annotation cp="🤟🏾" type="tts">love-you gesture: medium-dark skin tone</annotation> + <annotation cp="🤟🏿">dark skin tone | hand | ILY | love-you gesture</annotation> + <annotation cp="🤟🏿" type="tts">love-you gesture: dark skin tone</annotation> + <annotation cp="🤘🏻">finger | hand | horns | light skin tone | rock-on | sign of the horns</annotation> + <annotation cp="🤘🏻" type="tts">sign of the horns: light skin tone</annotation> + <annotation cp="🤘🏼">finger | hand | horns | medium-light skin tone | rock-on | sign of the horns</annotation> + <annotation cp="🤘🏼" type="tts">sign of the horns: medium-light skin tone</annotation> + <annotation cp="🤘🏽">finger | hand | horns | medium skin tone | rock-on | sign of the horns</annotation> + <annotation cp="🤘🏽" type="tts">sign of the horns: medium skin tone</annotation> + <annotation cp="🤘🏾">finger | hand | horns | medium-dark skin tone | rock-on | sign of the horns</annotation> + <annotation cp="🤘🏾" type="tts">sign of the horns: medium-dark skin tone</annotation> + <annotation cp="🤘🏿">dark skin tone | finger | hand | horns | rock-on | sign of the horns</annotation> + <annotation cp="🤘🏿" type="tts">sign of the horns: dark skin tone</annotation> + <annotation cp="🤙🏻">call | call me hand | hand | light skin tone</annotation> + <annotation cp="🤙🏻" type="tts">call me hand: light skin tone</annotation> + <annotation cp="🤙🏼">call | call me hand | hand | medium-light skin tone</annotation> + <annotation cp="🤙🏼" type="tts">call me hand: medium-light skin tone</annotation> + <annotation cp="🤙🏽">call | call me hand | hand | medium skin tone</annotation> + <annotation cp="🤙🏽" type="tts">call me hand: medium skin tone</annotation> + <annotation cp="🤙🏾">call | call me hand | hand | medium-dark skin tone</annotation> + <annotation cp="🤙🏾" type="tts">call me hand: medium-dark skin tone</annotation> + <annotation cp="🤙🏿">call | call me hand | dark skin tone | hand</annotation> + <annotation cp="🤙🏿" type="tts">call me hand: dark skin tone</annotation> + <annotation cp="👈🏻">backhand | backhand index pointing left | finger | hand | index | light skin tone | point</annotation> + <annotation cp="👈🏻" type="tts">backhand index pointing left: light skin tone</annotation> + <annotation cp="👈🏼">backhand | backhand index pointing left | finger | hand | index | medium-light skin tone | point</annotation> + <annotation cp="👈🏼" type="tts">backhand index pointing left: medium-light skin tone</annotation> + <annotation cp="👈🏽">backhand | backhand index pointing left | finger | hand | index | medium skin tone | point</annotation> + <annotation cp="👈🏽" type="tts">backhand index pointing left: medium skin tone</annotation> + <annotation cp="👈🏾">backhand | backhand index pointing left | finger | hand | index | medium-dark skin tone | point</annotation> + <annotation cp="👈🏾" type="tts">backhand index pointing left: medium-dark skin tone</annotation> + <annotation cp="👈🏿">backhand | backhand index pointing left | dark skin tone | finger | hand | index | point</annotation> + <annotation cp="👈🏿" type="tts">backhand index pointing left: dark skin tone</annotation> + <annotation cp="👉🏻">backhand | backhand index pointing right | finger | hand | index | light skin tone | point</annotation> + <annotation cp="👉🏻" type="tts">backhand index pointing right: light skin tone</annotation> + <annotation cp="👉🏼">backhand | backhand index pointing right | finger | hand | index | medium-light skin tone | point</annotation> + <annotation cp="👉🏼" type="tts">backhand index pointing right: medium-light skin tone</annotation> + <annotation cp="👉🏽">backhand | backhand index pointing right | finger | hand | index | medium skin tone | point</annotation> + <annotation cp="👉🏽" type="tts">backhand index pointing right: medium skin tone</annotation> + <annotation cp="👉🏾">backhand | backhand index pointing right | finger | hand | index | medium-dark skin tone | point</annotation> + <annotation cp="👉🏾" type="tts">backhand index pointing right: medium-dark skin tone</annotation> + <annotation cp="👉🏿">backhand | backhand index pointing right | dark skin tone | finger | hand | index | point</annotation> + <annotation cp="👉🏿" type="tts">backhand index pointing right: dark skin tone</annotation> + <annotation cp="👆🏻">backhand | backhand index pointing up | finger | hand | light skin tone | point | up</annotation> + <annotation cp="👆🏻" type="tts">backhand index pointing up: light skin tone</annotation> + <annotation cp="👆🏼">backhand | backhand index pointing up | finger | hand | medium-light skin tone | point | up</annotation> + <annotation cp="👆🏼" type="tts">backhand index pointing up: medium-light skin tone</annotation> + <annotation cp="👆🏽">backhand | backhand index pointing up | finger | hand | medium skin tone | point | up</annotation> + <annotation cp="👆🏽" type="tts">backhand index pointing up: medium skin tone</annotation> + <annotation cp="👆🏾">backhand | backhand index pointing up | finger | hand | medium-dark skin tone | point | up</annotation> + <annotation cp="👆🏾" type="tts">backhand index pointing up: medium-dark skin tone</annotation> + <annotation cp="👆🏿">backhand | backhand index pointing up | dark skin tone | finger | hand | point | up</annotation> + <annotation cp="👆🏿" type="tts">backhand index pointing up: dark skin tone</annotation> + <annotation cp="🖕🏻">finger | hand | light skin tone | middle finger</annotation> + <annotation cp="🖕🏻" type="tts">middle finger: light skin tone</annotation> + <annotation cp="🖕🏼">finger | hand | medium-light skin tone | middle finger</annotation> + <annotation cp="🖕🏼" type="tts">middle finger: medium-light skin tone</annotation> + <annotation cp="🖕🏽">finger | hand | medium skin tone | middle finger</annotation> + <annotation cp="🖕🏽" type="tts">middle finger: medium skin tone</annotation> + <annotation cp="🖕🏾">finger | hand | medium-dark skin tone | middle finger</annotation> + <annotation cp="🖕🏾" type="tts">middle finger: medium-dark skin tone</annotation> + <annotation cp="🖕🏿">dark skin tone | finger | hand | middle finger</annotation> + <annotation cp="🖕🏿" type="tts">middle finger: dark skin tone</annotation> + <annotation cp="👇🏻">backhand | backhand index pointing down | down | finger | hand | light skin tone | point</annotation> + <annotation cp="👇🏻" type="tts">backhand index pointing down: light skin tone</annotation> + <annotation cp="👇🏼">backhand | backhand index pointing down | down | finger | hand | medium-light skin tone | point</annotation> + <annotation cp="👇🏼" type="tts">backhand index pointing down: medium-light skin tone</annotation> + <annotation cp="👇🏽">backhand | backhand index pointing down | down | finger | hand | medium skin tone | point</annotation> + <annotation cp="👇🏽" type="tts">backhand index pointing down: medium skin tone</annotation> + <annotation cp="👇🏾">backhand | backhand index pointing down | down | finger | hand | medium-dark skin tone | point</annotation> + <annotation cp="👇🏾" type="tts">backhand index pointing down: medium-dark skin tone</annotation> + <annotation cp="👇🏿">backhand | backhand index pointing down | dark skin tone | down | finger | hand | point</annotation> + <annotation cp="👇🏿" type="tts">backhand index pointing down: dark skin tone</annotation> + <annotation cp="☝🏻">finger | hand | index | index pointing up | light skin tone | point | up</annotation> + <annotation cp="☝🏻" type="tts">index pointing up: light skin tone</annotation> + <annotation cp="☝🏼">finger | hand | index | index pointing up | medium-light skin tone | point | up</annotation> + <annotation cp="☝🏼" type="tts">index pointing up: medium-light skin tone</annotation> + <annotation cp="☝🏽">finger | hand | index | index pointing up | medium skin tone | point | up</annotation> + <annotation cp="☝🏽" type="tts">index pointing up: medium skin tone</annotation> + <annotation cp="☝🏾">finger | hand | index | index pointing up | medium-dark skin tone | point | up</annotation> + <annotation cp="☝🏾" type="tts">index pointing up: medium-dark skin tone</annotation> + <annotation cp="☝🏿">dark skin tone | finger | hand | index | index pointing up | point | up</annotation> + <annotation cp="☝🏿" type="tts">index pointing up: dark skin tone</annotation> + <annotation cp="👍🏻">+1 | hand | light skin tone | thumb | thumbs up | up</annotation> + <annotation cp="👍🏻" type="tts">thumbs up: light skin tone</annotation> + <annotation cp="👍🏼">+1 | hand | medium-light skin tone | thumb | thumbs up | up</annotation> + <annotation cp="👍🏼" type="tts">thumbs up: medium-light skin tone</annotation> + <annotation cp="👍🏽">+1 | hand | medium skin tone | thumb | thumbs up | up</annotation> + <annotation cp="👍🏽" type="tts">thumbs up: medium skin tone</annotation> + <annotation cp="👍🏾">+1 | hand | medium-dark skin tone | thumb | thumbs up | up</annotation> + <annotation cp="👍🏾" type="tts">thumbs up: medium-dark skin tone</annotation> + <annotation cp="👍🏿">+1 | dark skin tone | hand | thumb | thumbs up | up</annotation> + <annotation cp="👍🏿" type="tts">thumbs up: dark skin tone</annotation> + <annotation cp="👎🏻">-1 | down | hand | light skin tone | thumb | thumbs down</annotation> + <annotation cp="👎🏻" type="tts">thumbs down: light skin tone</annotation> + <annotation cp="👎🏼">-1 | down | hand | medium-light skin tone | thumb | thumbs down</annotation> + <annotation cp="👎🏼" type="tts">thumbs down: medium-light skin tone</annotation> + <annotation cp="👎🏽">-1 | down | hand | medium skin tone | thumb | thumbs down</annotation> + <annotation cp="👎🏽" type="tts">thumbs down: medium skin tone</annotation> + <annotation cp="👎🏾">-1 | down | hand | medium-dark skin tone | thumb | thumbs down</annotation> + <annotation cp="👎🏾" type="tts">thumbs down: medium-dark skin tone</annotation> + <annotation cp="👎🏿">-1 | dark skin tone | down | hand | thumb | thumbs down</annotation> + <annotation cp="👎🏿" type="tts">thumbs down: dark skin tone</annotation> + <annotation cp="✊🏻">clenched | fist | hand | light skin tone | punch | raised fist</annotation> + <annotation cp="✊🏻" type="tts">raised fist: light skin tone</annotation> + <annotation cp="✊🏼">clenched | fist | hand | medium-light skin tone | punch | raised fist</annotation> + <annotation cp="✊🏼" type="tts">raised fist: medium-light skin tone</annotation> + <annotation cp="✊🏽">clenched | fist | hand | medium skin tone | punch | raised fist</annotation> + <annotation cp="✊🏽" type="tts">raised fist: medium skin tone</annotation> + <annotation cp="✊🏾">clenched | fist | hand | medium-dark skin tone | punch | raised fist</annotation> + <annotation cp="✊🏾" type="tts">raised fist: medium-dark skin tone</annotation> + <annotation cp="✊🏿">clenched | dark skin tone | fist | hand | punch | raised fist</annotation> + <annotation cp="✊🏿" type="tts">raised fist: dark skin tone</annotation> + <annotation cp="👊🏻">clenched | fist | hand | light skin tone | oncoming fist | punch</annotation> + <annotation cp="👊🏻" type="tts">oncoming fist: light skin tone</annotation> + <annotation cp="👊🏼">clenched | fist | hand | medium-light skin tone | oncoming fist | punch</annotation> + <annotation cp="👊🏼" type="tts">oncoming fist: medium-light skin tone</annotation> + <annotation cp="👊🏽">clenched | fist | hand | medium skin tone | oncoming fist | punch</annotation> + <annotation cp="👊🏽" type="tts">oncoming fist: medium skin tone</annotation> + <annotation cp="👊🏾">clenched | fist | hand | medium-dark skin tone | oncoming fist | punch</annotation> + <annotation cp="👊🏾" type="tts">oncoming fist: medium-dark skin tone</annotation> + <annotation cp="👊🏿">clenched | dark skin tone | fist | hand | oncoming fist | punch</annotation> + <annotation cp="👊🏿" type="tts">oncoming fist: dark skin tone</annotation> + <annotation cp="🤛🏻">fist | left-facing fist | leftwards | light skin tone</annotation> + <annotation cp="🤛🏻" type="tts">left-facing fist: light skin tone</annotation> + <annotation cp="🤛🏼">fist | left-facing fist | leftwards | medium-light skin tone</annotation> + <annotation cp="🤛🏼" type="tts">left-facing fist: medium-light skin tone</annotation> + <annotation cp="🤛🏽">fist | left-facing fist | leftwards | medium skin tone</annotation> + <annotation cp="🤛🏽" type="tts">left-facing fist: medium skin tone</annotation> + <annotation cp="🤛🏾">fist | left-facing fist | leftwards | medium-dark skin tone</annotation> + <annotation cp="🤛🏾" type="tts">left-facing fist: medium-dark skin tone</annotation> + <annotation cp="🤛🏿">dark skin tone | fist | left-facing fist | leftwards</annotation> + <annotation cp="🤛🏿" type="tts">left-facing fist: dark skin tone</annotation> + <annotation cp="🤜🏻">fist | light skin tone | right-facing fist | rightwards</annotation> + <annotation cp="🤜🏻" type="tts">right-facing fist: light skin tone</annotation> + <annotation cp="🤜🏼">fist | medium-light skin tone | right-facing fist | rightwards</annotation> + <annotation cp="🤜🏼" type="tts">right-facing fist: medium-light skin tone</annotation> + <annotation cp="🤜🏽">fist | medium skin tone | right-facing fist | rightwards</annotation> + <annotation cp="🤜🏽" type="tts">right-facing fist: medium skin tone</annotation> + <annotation cp="🤜🏾">fist | medium-dark skin tone | right-facing fist | rightwards</annotation> + <annotation cp="🤜🏾" type="tts">right-facing fist: medium-dark skin tone</annotation> + <annotation cp="🤜🏿">dark skin tone | fist | right-facing fist | rightwards</annotation> + <annotation cp="🤜🏿" type="tts">right-facing fist: dark skin tone</annotation> + <annotation cp="👏🏻">clap | clapping hands | hand | light skin tone</annotation> + <annotation cp="👏🏻" type="tts">clapping hands: light skin tone</annotation> + <annotation cp="👏🏼">clap | clapping hands | hand | medium-light skin tone</annotation> + <annotation cp="👏🏼" type="tts">clapping hands: medium-light skin tone</annotation> + <annotation cp="👏🏽">clap | clapping hands | hand | medium skin tone</annotation> + <annotation cp="👏🏽" type="tts">clapping hands: medium skin tone</annotation> + <annotation cp="👏🏾">clap | clapping hands | hand | medium-dark skin tone</annotation> + <annotation cp="👏🏾" type="tts">clapping hands: medium-dark skin tone</annotation> + <annotation cp="👏🏿">clap | clapping hands | dark skin tone | hand</annotation> + <annotation cp="👏🏿" type="tts">clapping hands: dark skin tone</annotation> + <annotation cp="🙌🏻">celebration | gesture | hand | hooray | light skin tone | raised | raising hands</annotation> + <annotation cp="🙌🏻" type="tts">raising hands: light skin tone</annotation> + <annotation cp="🙌🏼">celebration | gesture | hand | hooray | medium-light skin tone | raised | raising hands</annotation> + <annotation cp="🙌🏼" type="tts">raising hands: medium-light skin tone</annotation> + <annotation cp="🙌🏽">celebration | gesture | hand | hooray | medium skin tone | raised | raising hands</annotation> + <annotation cp="🙌🏽" type="tts">raising hands: medium skin tone</annotation> + <annotation cp="🙌🏾">celebration | gesture | hand | hooray | medium-dark skin tone | raised | raising hands</annotation> + <annotation cp="🙌🏾" type="tts">raising hands: medium-dark skin tone</annotation> + <annotation cp="🙌🏿">celebration | dark skin tone | gesture | hand | hooray | raised | raising hands</annotation> + <annotation cp="🙌🏿" type="tts">raising hands: dark skin tone</annotation> + <annotation cp="👐🏻">hand | light skin tone | open | open hands</annotation> + <annotation cp="👐🏻" type="tts">open hands: light skin tone</annotation> + <annotation cp="👐🏼">hand | medium-light skin tone | open | open hands</annotation> + <annotation cp="👐🏼" type="tts">open hands: medium-light skin tone</annotation> + <annotation cp="👐🏽">hand | medium skin tone | open | open hands</annotation> + <annotation cp="👐🏽" type="tts">open hands: medium skin tone</annotation> + <annotation cp="👐🏾">hand | medium-dark skin tone | open | open hands</annotation> + <annotation cp="👐🏾" type="tts">open hands: medium-dark skin tone</annotation> + <annotation cp="👐🏿">dark skin tone | hand | open | open hands</annotation> + <annotation cp="👐🏿" type="tts">open hands: dark skin tone</annotation> + <annotation cp="🤲🏻">light skin tone | palms up together | prayer</annotation> + <annotation cp="🤲🏻" type="tts">palms up together: light skin tone</annotation> + <annotation cp="🤲🏼">medium-light skin tone | palms up together | prayer</annotation> + <annotation cp="🤲🏼" type="tts">palms up together: medium-light skin tone</annotation> + <annotation cp="🤲🏽">medium skin tone | palms up together | prayer</annotation> + <annotation cp="🤲🏽" type="tts">palms up together: medium skin tone</annotation> + <annotation cp="🤲🏾">medium-dark skin tone | palms up together | prayer</annotation> + <annotation cp="🤲🏾" type="tts">palms up together: medium-dark skin tone</annotation> + <annotation cp="🤲🏿">dark skin tone | palms up together | prayer</annotation> + <annotation cp="🤲🏿" type="tts">palms up together: dark skin tone</annotation> + <annotation cp="🙏🏻">ask | folded hands | hand | high 5 | high five | light skin tone | please | pray | thanks</annotation> + <annotation cp="🙏🏻" type="tts">folded hands: light skin tone</annotation> + <annotation cp="🙏🏼">ask | folded hands | hand | high 5 | high five | medium-light skin tone | please | pray | thanks</annotation> + <annotation cp="🙏🏼" type="tts">folded hands: medium-light skin tone</annotation> + <annotation cp="🙏🏽">ask | folded hands | hand | high 5 | high five | medium skin tone | please | pray | thanks</annotation> + <annotation cp="🙏🏽" type="tts">folded hands: medium skin tone</annotation> + <annotation cp="🙏🏾">ask | folded hands | hand | high 5 | high five | medium-dark skin tone | please | pray | thanks</annotation> + <annotation cp="🙏🏾" type="tts">folded hands: medium-dark skin tone</annotation> + <annotation cp="🙏🏿">ask | dark skin tone | folded hands | hand | high 5 | high five | please | pray | thanks</annotation> + <annotation cp="🙏🏿" type="tts">folded hands: dark skin tone</annotation> + <annotation cp="✍🏻">hand | light skin tone | write | writing hand</annotation> + <annotation cp="✍🏻" type="tts">writing hand: light skin tone</annotation> + <annotation cp="✍🏼">hand | medium-light skin tone | write | writing hand</annotation> + <annotation cp="✍🏼" type="tts">writing hand: medium-light skin tone</annotation> + <annotation cp="✍🏽">hand | medium skin tone | write | writing hand</annotation> + <annotation cp="✍🏽" type="tts">writing hand: medium skin tone</annotation> + <annotation cp="✍🏾">hand | medium-dark skin tone | write | writing hand</annotation> + <annotation cp="✍🏾" type="tts">writing hand: medium-dark skin tone</annotation> + <annotation cp="✍🏿">dark skin tone | hand | write | writing hand</annotation> + <annotation cp="✍🏿" type="tts">writing hand: dark skin tone</annotation> + <annotation cp="💅🏻">care | cosmetics | light skin tone | manicure | nail | polish</annotation> + <annotation cp="💅🏻" type="tts">nail polish: light skin tone</annotation> + <annotation cp="💅🏼">care | cosmetics | manicure | medium-light skin tone | nail | polish</annotation> + <annotation cp="💅🏼" type="tts">nail polish: medium-light skin tone</annotation> + <annotation cp="💅🏽">care | cosmetics | manicure | medium skin tone | nail | polish</annotation> + <annotation cp="💅🏽" type="tts">nail polish: medium skin tone</annotation> + <annotation cp="💅🏾">care | cosmetics | manicure | medium-dark skin tone | nail | polish</annotation> + <annotation cp="💅🏾" type="tts">nail polish: medium-dark skin tone</annotation> + <annotation cp="💅🏿">care | cosmetics | dark skin tone | manicure | nail | polish</annotation> + <annotation cp="💅🏿" type="tts">nail polish: dark skin tone</annotation> + <annotation cp="🤳🏻">camera | light skin tone | phone | selfie</annotation> + <annotation cp="🤳🏻" type="tts">selfie: light skin tone</annotation> + <annotation cp="🤳🏼">camera | medium-light skin tone | phone | selfie</annotation> + <annotation cp="🤳🏼" type="tts">selfie: medium-light skin tone</annotation> + <annotation cp="🤳🏽">camera | medium skin tone | phone | selfie</annotation> + <annotation cp="🤳🏽" type="tts">selfie: medium skin tone</annotation> + <annotation cp="🤳🏾">camera | medium-dark skin tone | phone | selfie</annotation> + <annotation cp="🤳🏾" type="tts">selfie: medium-dark skin tone</annotation> + <annotation cp="🤳🏿">camera | dark skin tone | phone | selfie</annotation> + <annotation cp="🤳🏿" type="tts">selfie: dark skin tone</annotation> + <annotation cp="💪🏻">biceps | comic | flex | flexed biceps | light skin tone | muscle</annotation> + <annotation cp="💪🏻" type="tts">flexed biceps: light skin tone</annotation> + <annotation cp="💪🏼">biceps | comic | flex | flexed biceps | medium-light skin tone | muscle</annotation> + <annotation cp="💪🏼" type="tts">flexed biceps: medium-light skin tone</annotation> + <annotation cp="💪🏽">biceps | comic | flex | flexed biceps | medium skin tone | muscle</annotation> + <annotation cp="💪🏽" type="tts">flexed biceps: medium skin tone</annotation> + <annotation cp="💪🏾">biceps | comic | flex | flexed biceps | medium-dark skin tone | muscle</annotation> + <annotation cp="💪🏾" type="tts">flexed biceps: medium-dark skin tone</annotation> + <annotation cp="💪🏿">biceps | comic | dark skin tone | flex | flexed biceps | muscle</annotation> + <annotation cp="💪🏿" type="tts">flexed biceps: dark skin tone</annotation> + <annotation cp="🦵🏻">kick | leg | light skin tone | limb</annotation> + <annotation cp="🦵🏻" type="tts">leg: light skin tone</annotation> + <annotation cp="🦵🏼">kick | leg | limb | medium-light skin tone</annotation> + <annotation cp="🦵🏼" type="tts">leg: medium-light skin tone</annotation> + <annotation cp="🦵🏽">kick | leg | limb | medium skin tone</annotation> + <annotation cp="🦵🏽" type="tts">leg: medium skin tone</annotation> + <annotation cp="🦵🏾">kick | leg | limb | medium-dark skin tone</annotation> + <annotation cp="🦵🏾" type="tts">leg: medium-dark skin tone</annotation> + <annotation cp="🦵🏿">dark skin tone | kick | leg | limb</annotation> + <annotation cp="🦵🏿" type="tts">leg: dark skin tone</annotation> + <annotation cp="🦶🏻">foot | kick | light skin tone | stomp</annotation> + <annotation cp="🦶🏻" type="tts">foot: light skin tone</annotation> + <annotation cp="🦶🏼">foot | kick | medium-light skin tone | stomp</annotation> + <annotation cp="🦶🏼" type="tts">foot: medium-light skin tone</annotation> + <annotation cp="🦶🏽">foot | kick | medium skin tone | stomp</annotation> + <annotation cp="🦶🏽" type="tts">foot: medium skin tone</annotation> + <annotation cp="🦶🏾">foot | kick | medium-dark skin tone | stomp</annotation> + <annotation cp="🦶🏾" type="tts">foot: medium-dark skin tone</annotation> + <annotation cp="🦶🏿">dark skin tone | foot | kick | stomp</annotation> + <annotation cp="🦶🏿" type="tts">foot: dark skin tone</annotation> + <annotation cp="👂🏻">body | ear | light skin tone</annotation> + <annotation cp="👂🏻" type="tts">ear: light skin tone</annotation> + <annotation cp="👂🏼">body | ear | medium-light skin tone</annotation> + <annotation cp="👂🏼" type="tts">ear: medium-light skin tone</annotation> + <annotation cp="👂🏽">body | ear | medium skin tone</annotation> + <annotation cp="👂🏽" type="tts">ear: medium skin tone</annotation> + <annotation cp="👂🏾">body | ear | medium-dark skin tone</annotation> + <annotation cp="👂🏾" type="tts">ear: medium-dark skin tone</annotation> + <annotation cp="👂🏿">body | dark skin tone | ear</annotation> + <annotation cp="👂🏿" type="tts">ear: dark skin tone</annotation> + <annotation cp="🦻🏻">accessibility | ear with hearing aid | hard of hearing | light skin tone</annotation> + <annotation cp="🦻🏻" type="tts">ear with hearing aid: light skin tone</annotation> + <annotation cp="🦻🏼">accessibility | ear with hearing aid | hard of hearing | medium-light skin tone</annotation> + <annotation cp="🦻🏼" type="tts">ear with hearing aid: medium-light skin tone</annotation> + <annotation cp="🦻🏽">accessibility | ear with hearing aid | hard of hearing | medium skin tone</annotation> + <annotation cp="🦻🏽" type="tts">ear with hearing aid: medium skin tone</annotation> + <annotation cp="🦻🏾">accessibility | ear with hearing aid | hard of hearing | medium-dark skin tone</annotation> + <annotation cp="🦻🏾" type="tts">ear with hearing aid: medium-dark skin tone</annotation> + <annotation cp="🦻🏿">accessibility | dark skin tone | ear with hearing aid | hard of hearing</annotation> + <annotation cp="🦻🏿" type="tts">ear with hearing aid: dark skin tone</annotation> + <annotation cp="👃🏻">body | light skin tone | nose</annotation> + <annotation cp="👃🏻" type="tts">nose: light skin tone</annotation> + <annotation cp="👃🏼">body | medium-light skin tone | nose</annotation> + <annotation cp="👃🏼" type="tts">nose: medium-light skin tone</annotation> + <annotation cp="👃🏽">body | medium skin tone | nose</annotation> + <annotation cp="👃🏽" type="tts">nose: medium skin tone</annotation> + <annotation cp="👃🏾">body | medium-dark skin tone | nose</annotation> + <annotation cp="👃🏾" type="tts">nose: medium-dark skin tone</annotation> + <annotation cp="👃🏿">body | dark skin tone | nose</annotation> + <annotation cp="👃🏿" type="tts">nose: dark skin tone</annotation> + <annotation cp="👶🏻">baby | light skin tone | young</annotation> + <annotation cp="👶🏻" type="tts">baby: light skin tone</annotation> + <annotation cp="👶🏼">baby | medium-light skin tone | young</annotation> + <annotation cp="👶🏼" type="tts">baby: medium-light skin tone</annotation> + <annotation cp="👶🏽">baby | medium skin tone | young</annotation> + <annotation cp="👶🏽" type="tts">baby: medium skin tone</annotation> + <annotation cp="👶🏾">baby | medium-dark skin tone | young</annotation> + <annotation cp="👶🏾" type="tts">baby: medium-dark skin tone</annotation> + <annotation cp="👶🏿">baby | dark skin tone | young</annotation> + <annotation cp="👶🏿" type="tts">baby: dark skin tone</annotation> + <annotation cp="🧒🏻">child | gender-neutral | light skin tone | unspecified gender | young</annotation> + <annotation cp="🧒🏻" type="tts">child: light skin tone</annotation> + <annotation cp="🧒🏼">child | gender-neutral | medium-light skin tone | unspecified gender | young</annotation> + <annotation cp="🧒🏼" type="tts">child: medium-light skin tone</annotation> + <annotation cp="🧒🏽">child | gender-neutral | medium skin tone | unspecified gender | young</annotation> + <annotation cp="🧒🏽" type="tts">child: medium skin tone</annotation> + <annotation cp="🧒🏾">child | gender-neutral | medium-dark skin tone | unspecified gender | young</annotation> + <annotation cp="🧒🏾" type="tts">child: medium-dark skin tone</annotation> + <annotation cp="🧒🏿">child | dark skin tone | gender-neutral | unspecified gender | young</annotation> + <annotation cp="🧒🏿" type="tts">child: dark skin tone</annotation> + <annotation cp="👦🏻">boy | light skin tone | young</annotation> + <annotation cp="👦🏻" type="tts">boy: light skin tone</annotation> + <annotation cp="👦🏼">boy | medium-light skin tone | young</annotation> + <annotation cp="👦🏼" type="tts">boy: medium-light skin tone</annotation> + <annotation cp="👦🏽">boy | medium skin tone | young</annotation> + <annotation cp="👦🏽" type="tts">boy: medium skin tone</annotation> + <annotation cp="👦🏾">boy | medium-dark skin tone | young</annotation> + <annotation cp="👦🏾" type="tts">boy: medium-dark skin tone</annotation> + <annotation cp="👦🏿">boy | dark skin tone | young</annotation> + <annotation cp="👦🏿" type="tts">boy: dark skin tone</annotation> + <annotation cp="👧🏻">girl | light skin tone | Virgo | young | zodiac</annotation> + <annotation cp="👧🏻" type="tts">girl: light skin tone</annotation> + <annotation cp="👧🏼">girl | medium-light skin tone | Virgo | young | zodiac</annotation> + <annotation cp="👧🏼" type="tts">girl: medium-light skin tone</annotation> + <annotation cp="👧🏽">girl | medium skin tone | Virgo | young | zodiac</annotation> + <annotation cp="👧🏽" type="tts">girl: medium skin tone</annotation> + <annotation cp="👧🏾">girl | medium-dark skin tone | Virgo | young | zodiac</annotation> + <annotation cp="👧🏾" type="tts">girl: medium-dark skin tone</annotation> + <annotation cp="👧🏿">dark skin tone | girl | Virgo | young | zodiac</annotation> + <annotation cp="👧🏿" type="tts">girl: dark skin tone</annotation> + <annotation cp="🧑🏻">adult | gender-neutral | light skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏻" type="tts">person: light skin tone</annotation> + <annotation cp="🧑🏼">adult | gender-neutral | medium-light skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏼" type="tts">person: medium-light skin tone</annotation> + <annotation cp="🧑🏽">adult | gender-neutral | medium skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏽" type="tts">person: medium skin tone</annotation> + <annotation cp="🧑🏾">adult | gender-neutral | medium-dark skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏾" type="tts">person: medium-dark skin tone</annotation> + <annotation cp="🧑🏿">adult | dark skin tone | gender-neutral | person | unspecified gender</annotation> + <annotation cp="🧑🏿" type="tts">person: dark skin tone</annotation> + <annotation cp="👱🏻">blond | blond-haired person | hair | light skin tone | person: blond hair</annotation> + <annotation cp="👱🏻" type="tts">person: light skin tone, blond hair</annotation> + <annotation cp="👱🏼">blond | blond-haired person | hair | medium-light skin tone | person: blond hair</annotation> + <annotation cp="👱🏼" type="tts">person: medium-light skin tone, blond hair</annotation> + <annotation cp="👱🏽">blond | blond-haired person | hair | medium skin tone | person: blond hair</annotation> + <annotation cp="👱🏽" type="tts">person: medium skin tone, blond hair</annotation> + <annotation cp="👱🏾">blond | blond-haired person | hair | medium-dark skin tone | person: blond hair</annotation> + <annotation cp="👱🏾" type="tts">person: medium-dark skin tone, blond hair</annotation> + <annotation cp="👱🏿">blond | blond-haired person | dark skin tone | hair | person: blond hair</annotation> + <annotation cp="👱🏿" type="tts">person: dark skin tone, blond hair</annotation> + <annotation cp="🧑🏻❤💋🧑🏼">couple | kiss | light skin tone | medium-light skin tone | person</annotation> + <annotation cp="🧑🏻❤💋🧑🏼" type="tts">kiss: person, person, light skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏻❤💋🧑🏽">couple | kiss | light skin tone | medium skin tone | person</annotation> + <annotation cp="🧑🏻❤💋🧑🏽" type="tts">kiss: person, person, light skin tone, medium skin tone</annotation> + <annotation cp="🧑🏻❤💋🧑🏾">couple | kiss | light skin tone | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏻❤💋🧑🏾" type="tts">kiss: person, person, light skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏻❤💋🧑🏿">couple | dark skin tone | kiss | light skin tone | person</annotation> + <annotation cp="🧑🏻❤💋🧑🏿" type="tts">kiss: person, person, light skin tone, dark skin tone</annotation> + <annotation cp="🧑🏼❤💋🧑🏻">couple | kiss | light skin tone | medium-light skin tone | person</annotation> + <annotation cp="🧑🏼❤💋🧑🏻" type="tts">kiss: person, person, medium-light skin tone, light skin tone</annotation> + <annotation cp="🧑🏼❤💋🧑🏽">couple | kiss | medium skin tone | medium-light skin tone | person</annotation> + <annotation cp="🧑🏼❤💋🧑🏽" type="tts">kiss: person, person, medium-light skin tone, medium skin tone</annotation> + <annotation cp="🧑🏼❤💋🧑🏾">couple | kiss | medium-dark skin tone | medium-light skin tone | person</annotation> + <annotation cp="🧑🏼❤💋🧑🏾" type="tts">kiss: person, person, medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏼❤💋🧑🏿">couple | dark skin tone | kiss | medium-light skin tone | person</annotation> + <annotation cp="🧑🏼❤💋🧑🏿" type="tts">kiss: person, person, medium-light skin tone, dark skin tone</annotation> + <annotation cp="🧑🏽❤💋🧑🏻">couple | kiss | light skin tone | medium skin tone | person</annotation> + <annotation cp="🧑🏽❤💋🧑🏻" type="tts">kiss: person, person, medium skin tone, light skin tone</annotation> + <annotation cp="🧑🏽❤💋🧑🏼">couple | kiss | medium skin tone | medium-light skin tone | person</annotation> + <annotation cp="🧑🏽❤💋🧑🏼" type="tts">kiss: person, person, medium skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏽❤💋🧑🏾">couple | kiss | medium skin tone | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏽❤💋🧑🏾" type="tts">kiss: person, person, medium skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏽❤💋🧑🏿">couple | dark skin tone | kiss | medium skin tone | person</annotation> + <annotation cp="🧑🏽❤💋🧑🏿" type="tts">kiss: person, person, medium skin tone, dark skin tone</annotation> + <annotation cp="🧑🏾❤💋🧑🏻">couple | kiss | light skin tone | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏾❤💋🧑🏻" type="tts">kiss: person, person, medium-dark skin tone, light skin tone</annotation> + <annotation cp="🧑🏾❤💋🧑🏼">couple | kiss | medium-dark skin tone | medium-light skin tone | person</annotation> + <annotation cp="🧑🏾❤💋🧑🏼" type="tts">kiss: person, person, medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏾❤💋🧑🏽">couple | kiss | medium skin tone | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏾❤💋🧑🏽" type="tts">kiss: person, person, medium-dark skin tone, medium skin tone</annotation> + <annotation cp="🧑🏾❤💋🧑🏿">couple | dark skin tone | kiss | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏾❤💋🧑🏿" type="tts">kiss: person, person, medium-dark skin tone, dark skin tone</annotation> + <annotation cp="🧑🏿❤💋🧑🏻">couple | dark skin tone | kiss | light skin tone | person</annotation> + <annotation cp="🧑🏿❤💋🧑🏻" type="tts">kiss: person, person, dark skin tone, light skin tone</annotation> + <annotation cp="🧑🏿❤💋🧑🏼">couple | dark skin tone | kiss | medium-light skin tone | person</annotation> + <annotation cp="🧑🏿❤💋🧑🏼" type="tts">kiss: person, person, dark skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏿❤💋🧑🏽">couple | dark skin tone | kiss | medium skin tone | person</annotation> + <annotation cp="🧑🏿❤💋🧑🏽" type="tts">kiss: person, person, dark skin tone, medium skin tone</annotation> + <annotation cp="🧑🏿❤💋🧑🏾">couple | dark skin tone | kiss | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏿❤💋🧑🏾" type="tts">kiss: person, person, dark skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏻❤🧑🏼">couple | couple with heart | light skin tone | love | medium-light skin tone | person</annotation> + <annotation cp="🧑🏻❤🧑🏼" type="tts">couple with heart: person, person, light skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏻❤🧑🏽">couple | couple with heart | light skin tone | love | medium skin tone | person</annotation> + <annotation cp="🧑🏻❤🧑🏽" type="tts">couple with heart: person, person, light skin tone, medium skin tone</annotation> + <annotation cp="🧑🏻❤🧑🏾">couple | couple with heart | light skin tone | love | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏻❤🧑🏾" type="tts">couple with heart: person, person, light skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏻❤🧑🏿">couple | couple with heart | dark skin tone | light skin tone | love | person</annotation> + <annotation cp="🧑🏻❤🧑🏿" type="tts">couple with heart: person, person, light skin tone, dark skin tone</annotation> + <annotation cp="🧑🏼❤🧑🏻">couple | couple with heart | light skin tone | love | medium-light skin tone | person</annotation> + <annotation cp="🧑🏼❤🧑🏻" type="tts">couple with heart: person, person, medium-light skin tone, light skin tone</annotation> + <annotation cp="🧑🏼❤🧑🏽">couple | couple with heart | love | medium skin tone | medium-light skin tone | person</annotation> + <annotation cp="🧑🏼❤🧑🏽" type="tts">couple with heart: person, person, medium-light skin tone, medium skin tone</annotation> + <annotation cp="🧑🏼❤🧑🏾">couple | couple with heart | love | medium-dark skin tone | medium-light skin tone | person</annotation> + <annotation cp="🧑🏼❤🧑🏾" type="tts">couple with heart: person, person, medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏼❤🧑🏿">couple | couple with heart | dark skin tone | love | medium-light skin tone | person</annotation> + <annotation cp="🧑🏼❤🧑🏿" type="tts">couple with heart: person, person, medium-light skin tone, dark skin tone</annotation> + <annotation cp="🧑🏽❤🧑🏻">couple | couple with heart | light skin tone | love | medium skin tone | person</annotation> + <annotation cp="🧑🏽❤🧑🏻" type="tts">couple with heart: person, person, medium skin tone, light skin tone</annotation> + <annotation cp="🧑🏽❤🧑🏼">couple | couple with heart | love | medium skin tone | medium-light skin tone | person</annotation> + <annotation cp="🧑🏽❤🧑🏼" type="tts">couple with heart: person, person, medium skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏽❤🧑🏾">couple | couple with heart | love | medium skin tone | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏽❤🧑🏾" type="tts">couple with heart: person, person, medium skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏽❤🧑🏿">couple | couple with heart | dark skin tone | love | medium skin tone | person</annotation> + <annotation cp="🧑🏽❤🧑🏿" type="tts">couple with heart: person, person, medium skin tone, dark skin tone</annotation> + <annotation cp="🧑🏾❤🧑🏻">couple | couple with heart | light skin tone | love | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏾❤🧑🏻" type="tts">couple with heart: person, person, medium-dark skin tone, light skin tone</annotation> + <annotation cp="🧑🏾❤🧑🏼">couple | couple with heart | love | medium-dark skin tone | medium-light skin tone | person</annotation> + <annotation cp="🧑🏾❤🧑🏼" type="tts">couple with heart: person, person, medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏾❤🧑🏽">couple | couple with heart | love | medium skin tone | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏾❤🧑🏽" type="tts">couple with heart: person, person, medium-dark skin tone, medium skin tone</annotation> + <annotation cp="🧑🏾❤🧑🏿">couple | couple with heart | dark skin tone | love | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏾❤🧑🏿" type="tts">couple with heart: person, person, medium-dark skin tone, dark skin tone</annotation> + <annotation cp="🧑🏿❤🧑🏻">couple | couple with heart | dark skin tone | light skin tone | love | person</annotation> + <annotation cp="🧑🏿❤🧑🏻" type="tts">couple with heart: person, person, dark skin tone, light skin tone</annotation> + <annotation cp="🧑🏿❤🧑🏼">couple | couple with heart | dark skin tone | love | medium-light skin tone | person</annotation> + <annotation cp="🧑🏿❤🧑🏼" type="tts">couple with heart: person, person, dark skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏿❤🧑🏽">couple | couple with heart | dark skin tone | love | medium skin tone | person</annotation> + <annotation cp="🧑🏿❤🧑🏽" type="tts">couple with heart: person, person, dark skin tone, medium skin tone</annotation> + <annotation cp="🧑🏿❤🧑🏾">couple | couple with heart | dark skin tone | love | medium-dark skin tone | person</annotation> + <annotation cp="🧑🏿❤🧑🏾" type="tts">couple with heart: person, person, dark skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🦰">adult | gender-neutral | person | red hair | unspecified gender</annotation> + <annotation cp="🧑🦰" type="tts">person: red hair</annotation> + <annotation cp="🧑🏻🦰">adult | gender-neutral | light skin tone | person | red hair | unspecified gender</annotation> + <annotation cp="🧑🏻🦰" type="tts">person: light skin tone, red hair</annotation> + <annotation cp="🧑🏼🦰">adult | gender-neutral | medium-light skin tone | person | red hair | unspecified gender</annotation> + <annotation cp="🧑🏼🦰" type="tts">person: medium-light skin tone, red hair</annotation> + <annotation cp="🧑🏽🦰">adult | gender-neutral | medium skin tone | person | red hair | unspecified gender</annotation> + <annotation cp="🧑🏽🦰" type="tts">person: medium skin tone, red hair</annotation> + <annotation cp="🧑🏾🦰">adult | gender-neutral | medium-dark skin tone | person | red hair | unspecified gender</annotation> + <annotation cp="🧑🏾🦰" type="tts">person: medium-dark skin tone, red hair</annotation> + <annotation cp="🧑🏿🦰">adult | dark skin tone | gender-neutral | person | red hair | unspecified gender</annotation> + <annotation cp="🧑🏿🦰" type="tts">person: dark skin tone, red hair</annotation> + <annotation cp="🧑🦱">adult | curly hair | gender-neutral | person | unspecified gender</annotation> + <annotation cp="🧑🦱" type="tts">person: curly hair</annotation> + <annotation cp="🧑🏻🦱">adult | curly hair | gender-neutral | light skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏻🦱" type="tts">person: light skin tone, curly hair</annotation> + <annotation cp="🧑🏼🦱">adult | curly hair | gender-neutral | medium-light skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏼🦱" type="tts">person: medium-light skin tone, curly hair</annotation> + <annotation cp="🧑🏽🦱">adult | curly hair | gender-neutral | medium skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏽🦱" type="tts">person: medium skin tone, curly hair</annotation> + <annotation cp="🧑🏾🦱">adult | curly hair | gender-neutral | medium-dark skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏾🦱" type="tts">person: medium-dark skin tone, curly hair</annotation> + <annotation cp="🧑🏿🦱">adult | curly hair | dark skin tone | gender-neutral | person | unspecified gender</annotation> + <annotation cp="🧑🏿🦱" type="tts">person: dark skin tone, curly hair</annotation> + <annotation cp="🧑🦳">adult | gender-neutral | person | unspecified gender | white hair</annotation> + <annotation cp="🧑🦳" type="tts">person: white hair</annotation> + <annotation cp="🧑🏻🦳">adult | gender-neutral | light skin tone | person | unspecified gender | white hair</annotation> + <annotation cp="🧑🏻🦳" type="tts">person: light skin tone, white hair</annotation> + <annotation cp="🧑🏼🦳">adult | gender-neutral | medium-light skin tone | person | unspecified gender | white hair</annotation> + <annotation cp="🧑🏼🦳" type="tts">person: medium-light skin tone, white hair</annotation> + <annotation cp="🧑🏽🦳">adult | gender-neutral | medium skin tone | person | unspecified gender | white hair</annotation> + <annotation cp="🧑🏽🦳" type="tts">person: medium skin tone, white hair</annotation> + <annotation cp="🧑🏾🦳">adult | gender-neutral | medium-dark skin tone | person | unspecified gender | white hair</annotation> + <annotation cp="🧑🏾🦳" type="tts">person: medium-dark skin tone, white hair</annotation> + <annotation cp="🧑🏿🦳">adult | dark skin tone | gender-neutral | person | unspecified gender | white hair</annotation> + <annotation cp="🧑🏿🦳" type="tts">person: dark skin tone, white hair</annotation> + <annotation cp="🧑🦲">adult | bald | gender-neutral | person | unspecified gender</annotation> + <annotation cp="🧑🦲" type="tts">person: bald</annotation> + <annotation cp="🧑🏻🦲">adult | bald | gender-neutral | light skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏻🦲" type="tts">person: light skin tone, bald</annotation> + <annotation cp="🧑🏼🦲">adult | bald | gender-neutral | medium-light skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏼🦲" type="tts">person: medium-light skin tone, bald</annotation> + <annotation cp="🧑🏽🦲">adult | bald | gender-neutral | medium skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏽🦲" type="tts">person: medium skin tone, bald</annotation> + <annotation cp="🧑🏾🦲">adult | bald | gender-neutral | medium-dark skin tone | person | unspecified gender</annotation> + <annotation cp="🧑🏾🦲" type="tts">person: medium-dark skin tone, bald</annotation> + <annotation cp="🧑🏿🦲">adult | bald | dark skin tone | gender-neutral | person | unspecified gender</annotation> + <annotation cp="🧑🏿🦲" type="tts">person: dark skin tone, bald</annotation> + <annotation cp="👨🏻">adult | light skin tone | man</annotation> + <annotation cp="👨🏻" type="tts">man: light skin tone</annotation> + <annotation cp="👨🏼">adult | man | medium-light skin tone</annotation> + <annotation cp="👨🏼" type="tts">man: medium-light skin tone</annotation> + <annotation cp="👨🏽">adult | man | medium skin tone</annotation> + <annotation cp="👨🏽" type="tts">man: medium skin tone</annotation> + <annotation cp="👨🏾">adult | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾" type="tts">man: medium-dark skin tone</annotation> + <annotation cp="👨🏿">adult | dark skin tone | man</annotation> + <annotation cp="👨🏿" type="tts">man: dark skin tone</annotation> + <annotation cp="🧔🏻">beard | light skin tone | person | person: beard</annotation> + <annotation cp="🧔🏻" type="tts">person: light skin tone, beard</annotation> + <annotation cp="🧔🏼">beard | medium-light skin tone | person | person: beard</annotation> + <annotation cp="🧔🏼" type="tts">person: medium-light skin tone, beard</annotation> + <annotation cp="🧔🏽">beard | medium skin tone | person | person: beard</annotation> + <annotation cp="🧔🏽" type="tts">person: medium skin tone, beard</annotation> + <annotation cp="🧔🏾">beard | medium-dark skin tone | person | person: beard</annotation> + <annotation cp="🧔🏾" type="tts">person: medium-dark skin tone, beard</annotation> + <annotation cp="🧔🏿">beard | dark skin tone | person | person: beard</annotation> + <annotation cp="🧔🏿" type="tts">person: dark skin tone, beard</annotation> + <annotation cp="🧔🏻♂">beard | light skin tone | man | man: beard</annotation> + <annotation cp="🧔🏻♂" type="tts">man: light skin tone, beard</annotation> + <annotation cp="🧔🏼♂">beard | man | man: beard | medium-light skin tone</annotation> + <annotation cp="🧔🏼♂" type="tts">man: medium-light skin tone, beard</annotation> + <annotation cp="🧔🏽♂">beard | man | man: beard | medium skin tone</annotation> + <annotation cp="🧔🏽♂" type="tts">man: medium skin tone, beard</annotation> + <annotation cp="🧔🏾♂">beard | man | man: beard | medium-dark skin tone</annotation> + <annotation cp="🧔🏾♂" type="tts">man: medium-dark skin tone, beard</annotation> + <annotation cp="🧔🏿♂">beard | dark skin tone | man | man: beard</annotation> + <annotation cp="🧔🏿♂" type="tts">man: dark skin tone, beard</annotation> + <annotation cp="👱🏻♂">blond | blond-haired man | hair | light skin tone | man | man: blond hair</annotation> + <annotation cp="👱🏻♂" type="tts">man: light skin tone, blond hair</annotation> + <annotation cp="👱🏼♂">blond | blond-haired man | hair | man | man: blond hair | medium-light skin tone</annotation> + <annotation cp="👱🏼♂" type="tts">man: medium-light skin tone, blond hair</annotation> + <annotation cp="👱🏽♂">blond | blond-haired man | hair | man | man: blond hair | medium skin tone</annotation> + <annotation cp="👱🏽♂" type="tts">man: medium skin tone, blond hair</annotation> + <annotation cp="👱🏾♂">blond | blond-haired man | hair | man | man: blond hair | medium-dark skin tone</annotation> + <annotation cp="👱🏾♂" type="tts">man: medium-dark skin tone, blond hair</annotation> + <annotation cp="👱🏿♂">blond | blond-haired man | dark skin tone | hair | man | man: blond hair</annotation> + <annotation cp="👱🏿♂" type="tts">man: dark skin tone, blond hair</annotation> + <annotation cp="👨🏻❤💋👨🏻">couple | kiss | light skin tone | man</annotation> + <annotation cp="👨🏻❤💋👨🏻" type="tts">kiss: man, man, light skin tone</annotation> + <annotation cp="👨🏻❤💋👨🏼">couple | kiss | light skin tone | man | medium-light skin tone</annotation> + <annotation cp="👨🏻❤💋👨🏼" type="tts">kiss: man, man, light skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏻❤💋👨🏽">couple | kiss | light skin tone | man | medium skin tone</annotation> + <annotation cp="👨🏻❤💋👨🏽" type="tts">kiss: man, man, light skin tone, medium skin tone</annotation> + <annotation cp="👨🏻❤💋👨🏾">couple | kiss | light skin tone | man | medium-dark skin tone</annotation> + <annotation cp="👨🏻❤💋👨🏾" type="tts">kiss: man, man, light skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏻❤💋👨🏿">couple | dark skin tone | kiss | light skin tone | man</annotation> + <annotation cp="👨🏻❤💋👨🏿" type="tts">kiss: man, man, light skin tone, dark skin tone</annotation> + <annotation cp="👨🏼❤💋👨🏻">couple | kiss | light skin tone | man | medium-light skin tone</annotation> + <annotation cp="👨🏼❤💋👨🏻" type="tts">kiss: man, man, medium-light skin tone, light skin tone</annotation> + <annotation cp="👨🏼❤💋👨🏼">couple | kiss | man | medium-light skin tone</annotation> + <annotation cp="👨🏼❤💋👨🏼" type="tts">kiss: man, man, medium-light skin tone</annotation> + <annotation cp="👨🏼❤💋👨🏽">couple | kiss | man | medium skin tone | medium-light skin tone</annotation> + <annotation cp="👨🏼❤💋👨🏽" type="tts">kiss: man, man, medium-light skin tone, medium skin tone</annotation> + <annotation cp="👨🏼❤💋👨🏾">couple | kiss | man | medium-dark skin tone | medium-light skin tone</annotation> + <annotation cp="👨🏼❤💋👨🏾" type="tts">kiss: man, man, medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏼❤💋👨🏿">couple | dark skin tone | kiss | man | medium-light skin tone</annotation> + <annotation cp="👨🏼❤💋👨🏿" type="tts">kiss: man, man, medium-light skin tone, dark skin tone</annotation> + <annotation cp="👨🏽❤💋👨🏻">couple | kiss | light skin tone | man | medium skin tone</annotation> + <annotation cp="👨🏽❤💋👨🏻" type="tts">kiss: man, man, medium skin tone, light skin tone</annotation> + <annotation cp="👨🏽❤💋👨🏼">couple | kiss | man | medium skin tone | medium-light skin tone</annotation> + <annotation cp="👨🏽❤💋👨🏼" type="tts">kiss: man, man, medium skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏽❤💋👨🏽">couple | kiss | man | medium skin tone</annotation> + <annotation cp="👨🏽❤💋👨🏽" type="tts">kiss: man, man, medium skin tone</annotation> + <annotation cp="👨🏽❤💋👨🏾">couple | kiss | man | medium skin tone | medium-dark skin tone</annotation> + <annotation cp="👨🏽❤💋👨🏾" type="tts">kiss: man, man, medium skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏽❤💋👨🏿">couple | dark skin tone | kiss | man | medium skin tone</annotation> + <annotation cp="👨🏽❤💋👨🏿" type="tts">kiss: man, man, medium skin tone, dark skin tone</annotation> + <annotation cp="👨🏾❤💋👨🏻">couple | kiss | light skin tone | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾❤💋👨🏻" type="tts">kiss: man, man, medium-dark skin tone, light skin tone</annotation> + <annotation cp="👨🏾❤💋👨🏼">couple | kiss | man | medium-dark skin tone | medium-light skin tone</annotation> + <annotation cp="👨🏾❤💋👨🏼" type="tts">kiss: man, man, medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏾❤💋👨🏽">couple | kiss | man | medium skin tone | medium-dark skin tone</annotation> + <annotation cp="👨🏾❤💋👨🏽" type="tts">kiss: man, man, medium-dark skin tone, medium skin tone</annotation> + <annotation cp="👨🏾❤💋👨🏾">couple | kiss | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾❤💋👨🏾" type="tts">kiss: man, man, medium-dark skin tone</annotation> + <annotation cp="👨🏾❤💋👨🏿">couple | dark skin tone | kiss | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾❤💋👨🏿" type="tts">kiss: man, man, medium-dark skin tone, dark skin tone</annotation> + <annotation cp="👨🏿❤💋👨🏻">couple | dark skin tone | kiss | light skin tone | man</annotation> + <annotation cp="👨🏿❤💋👨🏻" type="tts">kiss: man, man, dark skin tone, light skin tone</annotation> + <annotation cp="👨🏿❤💋👨🏼">couple | dark skin tone | kiss | man | medium-light skin tone</annotation> + <annotation cp="👨🏿❤💋👨🏼" type="tts">kiss: man, man, dark skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏿❤💋👨🏽">couple | dark skin tone | kiss | man | medium skin tone</annotation> + <annotation cp="👨🏿❤💋👨🏽" type="tts">kiss: man, man, dark skin tone, medium skin tone</annotation> + <annotation cp="👨🏿❤💋👨🏾">couple | dark skin tone | kiss | man | medium-dark skin tone</annotation> + <annotation cp="👨🏿❤💋👨🏾" type="tts">kiss: man, man, dark skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏿❤💋👨🏿">couple | dark skin tone | kiss | man</annotation> + <annotation cp="👨🏿❤💋👨🏿" type="tts">kiss: man, man, dark skin tone</annotation> + <annotation cp="👨🏻❤👨🏻">couple | couple with heart | light skin tone | love | man</annotation> + <annotation cp="👨🏻❤👨🏻" type="tts">couple with heart: man, man, light skin tone</annotation> + <annotation cp="👨🏻❤👨🏼">couple | couple with heart | light skin tone | love | man | medium-light skin tone</annotation> + <annotation cp="👨🏻❤👨🏼" type="tts">couple with heart: man, man, light skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏻❤👨🏽">couple | couple with heart | light skin tone | love | man | medium skin tone</annotation> + <annotation cp="👨🏻❤👨🏽" type="tts">couple with heart: man, man, light skin tone, medium skin tone</annotation> + <annotation cp="👨🏻❤👨🏾">couple | couple with heart | light skin tone | love | man | medium-dark skin tone</annotation> + <annotation cp="👨🏻❤👨🏾" type="tts">couple with heart: man, man, light skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏻❤👨🏿">couple | couple with heart | dark skin tone | light skin tone | love | man</annotation> + <annotation cp="👨🏻❤👨🏿" type="tts">couple with heart: man, man, light skin tone, dark skin tone</annotation> + <annotation cp="👨🏼❤👨🏻">couple | couple with heart | light skin tone | love | man | medium-light skin tone</annotation> + <annotation cp="👨🏼❤👨🏻" type="tts">couple with heart: man, man, medium-light skin tone, light skin tone</annotation> + <annotation cp="👨🏼❤👨🏼">couple | couple with heart | love | man | medium-light skin tone</annotation> + <annotation cp="👨🏼❤👨🏼" type="tts">couple with heart: man, man, medium-light skin tone</annotation> + <annotation cp="👨🏼❤👨🏽">couple | couple with heart | love | man | medium skin tone | medium-light skin tone</annotation> + <annotation cp="👨🏼❤👨🏽" type="tts">couple with heart: man, man, medium-light skin tone, medium skin tone</annotation> + <annotation cp="👨🏼❤👨🏾">couple | couple with heart | love | man | medium-dark skin tone | medium-light skin tone</annotation> + <annotation cp="👨🏼❤👨🏾" type="tts">couple with heart: man, man, medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏼❤👨🏿">couple | couple with heart | dark skin tone | love | man | medium-light skin tone</annotation> + <annotation cp="👨🏼❤👨🏿" type="tts">couple with heart: man, man, medium-light skin tone, dark skin tone</annotation> + <annotation cp="👨🏽❤👨🏻">couple | couple with heart | light skin tone | love | man | medium skin tone</annotation> + <annotation cp="👨🏽❤👨🏻" type="tts">couple with heart: man, man, medium skin tone, light skin tone</annotation> + <annotation cp="👨🏽❤👨🏼">couple | couple with heart | love | man | medium skin tone | medium-light skin tone</annotation> + <annotation cp="👨🏽❤👨🏼" type="tts">couple with heart: man, man, medium skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏽❤👨🏽">couple | couple with heart | love | man | medium skin tone</annotation> + <annotation cp="👨🏽❤👨🏽" type="tts">couple with heart: man, man, medium skin tone</annotation> + <annotation cp="👨🏽❤👨🏾">couple | couple with heart | love | man | medium skin tone | medium-dark skin tone</annotation> + <annotation cp="👨🏽❤👨🏾" type="tts">couple with heart: man, man, medium skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏽❤👨🏿">couple | couple with heart | dark skin tone | love | man | medium skin tone</annotation> + <annotation cp="👨🏽❤👨🏿" type="tts">couple with heart: man, man, medium skin tone, dark skin tone</annotation> + <annotation cp="👨🏾❤👨🏻">couple | couple with heart | light skin tone | love | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾❤👨🏻" type="tts">couple with heart: man, man, medium-dark skin tone, light skin tone</annotation> + <annotation cp="👨🏾❤👨🏼">couple | couple with heart | love | man | medium-dark skin tone | medium-light skin tone</annotation> + <annotation cp="👨🏾❤👨🏼" type="tts">couple with heart: man, man, medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏾❤👨🏽">couple | couple with heart | love | man | medium skin tone | medium-dark skin tone</annotation> + <annotation cp="👨🏾❤👨🏽" type="tts">couple with heart: man, man, medium-dark skin tone, medium skin tone</annotation> + <annotation cp="👨🏾❤👨🏾">couple | couple with heart | love | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾❤👨🏾" type="tts">couple with heart: man, man, medium-dark skin tone</annotation> + <annotation cp="👨🏾❤👨🏿">couple | couple with heart | dark skin tone | love | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾❤👨🏿" type="tts">couple with heart: man, man, medium-dark skin tone, dark skin tone</annotation> + <annotation cp="👨🏿❤👨🏻">couple | couple with heart | dark skin tone | light skin tone | love | man</annotation> + <annotation cp="👨🏿❤👨🏻" type="tts">couple with heart: man, man, dark skin tone, light skin tone</annotation> + <annotation cp="👨🏿❤👨🏼">couple | couple with heart | dark skin tone | love | man | medium-light skin tone</annotation> + <annotation cp="👨🏿❤👨🏼" type="tts">couple with heart: man, man, dark skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏿❤👨🏽">couple | couple with heart | dark skin tone | love | man | medium skin tone</annotation> + <annotation cp="👨🏿❤👨🏽" type="tts">couple with heart: man, man, dark skin tone, medium skin tone</annotation> + <annotation cp="👨🏿❤👨🏾">couple | couple with heart | dark skin tone | love | man | medium-dark skin tone</annotation> + <annotation cp="👨🏿❤👨🏾" type="tts">couple with heart: man, man, dark skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏿❤👨🏿">couple | couple with heart | dark skin tone | love | man</annotation> + <annotation cp="👨🏿❤👨🏿" type="tts">couple with heart: man, man, dark skin tone</annotation> + <annotation cp="👨🦰">adult | man | red hair</annotation> + <annotation cp="👨🦰" type="tts">man: red hair</annotation> + <annotation cp="👨🏻🦰">adult | light skin tone | man | red hair</annotation> + <annotation cp="👨🏻🦰" type="tts">man: light skin tone, red hair</annotation> + <annotation cp="👨🏼🦰">adult | man | medium-light skin tone | red hair</annotation> + <annotation cp="👨🏼🦰" type="tts">man: medium-light skin tone, red hair</annotation> + <annotation cp="👨🏽🦰">adult | man | medium skin tone | red hair</annotation> + <annotation cp="👨🏽🦰" type="tts">man: medium skin tone, red hair</annotation> + <annotation cp="👨🏾🦰">adult | man | medium-dark skin tone | red hair</annotation> + <annotation cp="👨🏾🦰" type="tts">man: medium-dark skin tone, red hair</annotation> + <annotation cp="👨🏿🦰">adult | dark skin tone | man | red hair</annotation> + <annotation cp="👨🏿🦰" type="tts">man: dark skin tone, red hair</annotation> + <annotation cp="👨🦱">adult | curly hair | man</annotation> + <annotation cp="👨🦱" type="tts">man: curly hair</annotation> + <annotation cp="👨🏻🦱">adult | curly hair | light skin tone | man</annotation> + <annotation cp="👨🏻🦱" type="tts">man: light skin tone, curly hair</annotation> + <annotation cp="👨🏼🦱">adult | curly hair | man | medium-light skin tone</annotation> + <annotation cp="👨🏼🦱" type="tts">man: medium-light skin tone, curly hair</annotation> + <annotation cp="👨🏽🦱">adult | curly hair | man | medium skin tone</annotation> + <annotation cp="👨🏽🦱" type="tts">man: medium skin tone, curly hair</annotation> + <annotation cp="👨🏾🦱">adult | curly hair | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾🦱" type="tts">man: medium-dark skin tone, curly hair</annotation> + <annotation cp="👨🏿🦱">adult | curly hair | dark skin tone | man</annotation> + <annotation cp="👨🏿🦱" type="tts">man: dark skin tone, curly hair</annotation> + <annotation cp="👨🦳">adult | man | white hair</annotation> + <annotation cp="👨🦳" type="tts">man: white hair</annotation> + <annotation cp="👨🏻🦳">adult | light skin tone | man | white hair</annotation> + <annotation cp="👨🏻🦳" type="tts">man: light skin tone, white hair</annotation> + <annotation cp="👨🏼🦳">adult | man | medium-light skin tone | white hair</annotation> + <annotation cp="👨🏼🦳" type="tts">man: medium-light skin tone, white hair</annotation> + <annotation cp="👨🏽🦳">adult | man | medium skin tone | white hair</annotation> + <annotation cp="👨🏽🦳" type="tts">man: medium skin tone, white hair</annotation> + <annotation cp="👨🏾🦳">adult | man | medium-dark skin tone | white hair</annotation> + <annotation cp="👨🏾🦳" type="tts">man: medium-dark skin tone, white hair</annotation> + <annotation cp="👨🏿🦳">adult | dark skin tone | man | white hair</annotation> + <annotation cp="👨🏿🦳" type="tts">man: dark skin tone, white hair</annotation> + <annotation cp="👨🦲">adult | bald | man</annotation> + <annotation cp="👨🦲" type="tts">man: bald</annotation> + <annotation cp="👨🏻🦲">adult | bald | light skin tone | man</annotation> + <annotation cp="👨🏻🦲" type="tts">man: light skin tone, bald</annotation> + <annotation cp="👨🏼🦲">adult | bald | man | medium-light skin tone</annotation> + <annotation cp="👨🏼🦲" type="tts">man: medium-light skin tone, bald</annotation> + <annotation cp="👨🏽🦲">adult | bald | man | medium skin tone</annotation> + <annotation cp="👨🏽🦲" type="tts">man: medium skin tone, bald</annotation> + <annotation cp="👨🏾🦲">adult | bald | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾🦲" type="tts">man: medium-dark skin tone, bald</annotation> + <annotation cp="👨🏿🦲">adult | bald | dark skin tone | man</annotation> + <annotation cp="👨🏿🦲" type="tts">man: dark skin tone, bald</annotation> + <annotation cp="👩🏻">adult | light skin tone | woman</annotation> + <annotation cp="👩🏻" type="tts">woman: light skin tone</annotation> + <annotation cp="👩🏼">adult | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼" type="tts">woman: medium-light skin tone</annotation> + <annotation cp="👩🏽">adult | medium skin tone | woman</annotation> + <annotation cp="👩🏽" type="tts">woman: medium skin tone</annotation> + <annotation cp="👩🏾">adult | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾" type="tts">woman: medium-dark skin tone</annotation> + <annotation cp="👩🏿">adult | dark skin tone | woman</annotation> + <annotation cp="👩🏿" type="tts">woman: dark skin tone</annotation> + <annotation cp="🧔🏻♀">beard | light skin tone | woman | woman: beard</annotation> + <annotation cp="🧔🏻♀" type="tts">woman: light skin tone, beard</annotation> + <annotation cp="🧔🏼♀">beard | medium-light skin tone | woman | woman: beard</annotation> + <annotation cp="🧔🏼♀" type="tts">woman: medium-light skin tone, beard</annotation> + <annotation cp="🧔🏽♀">beard | medium skin tone | woman | woman: beard</annotation> + <annotation cp="🧔🏽♀" type="tts">woman: medium skin tone, beard</annotation> + <annotation cp="🧔🏾♀">beard | medium-dark skin tone | woman | woman: beard</annotation> + <annotation cp="🧔🏾♀" type="tts">woman: medium-dark skin tone, beard</annotation> + <annotation cp="🧔🏿♀">beard | dark skin tone | woman | woman: beard</annotation> + <annotation cp="🧔🏿♀" type="tts">woman: dark skin tone, beard</annotation> + <annotation cp="👱🏻♀">blond hair | blond-haired woman | blonde | hair | light skin tone | woman | woman: blond hair</annotation> + <annotation cp="👱🏻♀" type="tts">woman: light skin tone, blond hair</annotation> + <annotation cp="👱🏼♀">blond hair | blond-haired woman | blonde | hair | medium-light skin tone | woman | woman: blond hair</annotation> + <annotation cp="👱🏼♀" type="tts">woman: medium-light skin tone, blond hair</annotation> + <annotation cp="👱🏽♀">blond hair | blond-haired woman | blonde | hair | medium skin tone | woman | woman: blond hair</annotation> + <annotation cp="👱🏽♀" type="tts">woman: medium skin tone, blond hair</annotation> + <annotation cp="👱🏾♀">blond hair | blond-haired woman | blonde | hair | medium-dark skin tone | woman | woman: blond hair</annotation> + <annotation cp="👱🏾♀" type="tts">woman: medium-dark skin tone, blond hair</annotation> + <annotation cp="👱🏿♀">blond hair | blond-haired woman | blonde | dark skin tone | hair | woman | woman: blond hair</annotation> + <annotation cp="👱🏿♀" type="tts">woman: dark skin tone, blond hair</annotation> + <annotation cp="👩🏻❤💋👨🏻">couple | kiss | light skin tone | man | woman</annotation> + <annotation cp="👩🏻❤💋👨🏻" type="tts">kiss: woman, man, light skin tone</annotation> + <annotation cp="👩🏻❤💋👨🏼">couple | kiss | light skin tone | man | medium-light skin tone | woman</annotation> + <annotation cp="👩🏻❤💋👨🏼" type="tts">kiss: woman, man, light skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏻❤💋👨🏽">couple | kiss | light skin tone | man | medium skin tone | woman</annotation> + <annotation cp="👩🏻❤💋👨🏽" type="tts">kiss: woman, man, light skin tone, medium skin tone</annotation> + <annotation cp="👩🏻❤💋👨🏾">couple | kiss | light skin tone | man | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏻❤💋👨🏾" type="tts">kiss: woman, man, light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏻❤💋👨🏿">couple | dark skin tone | kiss | light skin tone | man | woman</annotation> + <annotation cp="👩🏻❤💋👨🏿" type="tts">kiss: woman, man, light skin tone, dark skin tone</annotation> + <annotation cp="👩🏼❤💋👨🏻">couple | kiss | light skin tone | man | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤💋👨🏻" type="tts">kiss: woman, man, medium-light skin tone, light skin tone</annotation> + <annotation cp="👩🏼❤💋👨🏼">couple | kiss | man | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤💋👨🏼" type="tts">kiss: woman, man, medium-light skin tone</annotation> + <annotation cp="👩🏼❤💋👨🏽">couple | kiss | man | medium skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤💋👨🏽" type="tts">kiss: woman, man, medium-light skin tone, medium skin tone</annotation> + <annotation cp="👩🏼❤💋👨🏾">couple | kiss | man | medium-dark skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤💋👨🏾" type="tts">kiss: woman, man, medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏼❤💋👨🏿">couple | dark skin tone | kiss | man | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤💋👨🏿" type="tts">kiss: woman, man, medium-light skin tone, dark skin tone</annotation> + <annotation cp="👩🏽❤💋👨🏻">couple | kiss | light skin tone | man | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤💋👨🏻" type="tts">kiss: woman, man, medium skin tone, light skin tone</annotation> + <annotation cp="👩🏽❤💋👨🏼">couple | kiss | man | medium skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏽❤💋👨🏼" type="tts">kiss: woman, man, medium skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏽❤💋👨🏽">couple | kiss | man | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤💋👨🏽" type="tts">kiss: woman, man, medium skin tone</annotation> + <annotation cp="👩🏽❤💋👨🏾">couple | kiss | man | medium skin tone | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏽❤💋👨🏾" type="tts">kiss: woman, man, medium skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏽❤💋👨🏿">couple | dark skin tone | kiss | man | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤💋👨🏿" type="tts">kiss: woman, man, medium skin tone, dark skin tone</annotation> + <annotation cp="👩🏾❤💋👨🏻">couple | kiss | light skin tone | man | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤💋👨🏻" type="tts">kiss: woman, man, medium-dark skin tone, light skin tone</annotation> + <annotation cp="👩🏾❤💋👨🏼">couple | kiss | man | medium-dark skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏾❤💋👨🏼" type="tts">kiss: woman, man, medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏾❤💋👨🏽">couple | kiss | man | medium skin tone | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤💋👨🏽" type="tts">kiss: woman, man, medium-dark skin tone, medium skin tone</annotation> + <annotation cp="👩🏾❤💋👨🏾">couple | kiss | man | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤💋👨🏾" type="tts">kiss: woman, man, medium-dark skin tone</annotation> + <annotation cp="👩🏾❤💋👨🏿">couple | dark skin tone | kiss | man | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤💋👨🏿" type="tts">kiss: woman, man, medium-dark skin tone, dark skin tone</annotation> + <annotation cp="👩🏿❤💋👨🏻">couple | dark skin tone | kiss | light skin tone | man | woman</annotation> + <annotation cp="👩🏿❤💋👨🏻" type="tts">kiss: woman, man, dark skin tone, light skin tone</annotation> + <annotation cp="👩🏿❤💋👨🏼">couple | dark skin tone | kiss | man | medium-light skin tone | woman</annotation> + <annotation cp="👩🏿❤💋👨🏼" type="tts">kiss: woman, man, dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏿❤💋👨🏽">couple | dark skin tone | kiss | man | medium skin tone | woman</annotation> + <annotation cp="👩🏿❤💋👨🏽" type="tts">kiss: woman, man, dark skin tone, medium skin tone</annotation> + <annotation cp="👩🏿❤💋👨🏾">couple | dark skin tone | kiss | man | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏿❤💋👨🏾" type="tts">kiss: woman, man, dark skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏿❤💋👨🏿">couple | dark skin tone | kiss | man | woman</annotation> + <annotation cp="👩🏿❤💋👨🏿" type="tts">kiss: woman, man, dark skin tone</annotation> + <annotation cp="👩🏻❤💋👩🏻">couple | kiss | light skin tone | woman</annotation> + <annotation cp="👩🏻❤💋👩🏻" type="tts">kiss: woman, woman, light skin tone</annotation> + <annotation cp="👩🏻❤💋👩🏼">couple | kiss | light skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏻❤💋👩🏼" type="tts">kiss: woman, woman, light skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏻❤💋👩🏽">couple | kiss | light skin tone | medium skin tone | woman</annotation> + <annotation cp="👩🏻❤💋👩🏽" type="tts">kiss: woman, woman, light skin tone, medium skin tone</annotation> + <annotation cp="👩🏻❤💋👩🏾">couple | kiss | light skin tone | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏻❤💋👩🏾" type="tts">kiss: woman, woman, light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏻❤💋👩🏿">couple | dark skin tone | kiss | light skin tone | woman</annotation> + <annotation cp="👩🏻❤💋👩🏿" type="tts">kiss: woman, woman, light skin tone, dark skin tone</annotation> + <annotation cp="👩🏼❤💋👩🏻">couple | kiss | light skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤💋👩🏻" type="tts">kiss: woman, woman, medium-light skin tone, light skin tone</annotation> + <annotation cp="👩🏼❤💋👩🏼">couple | kiss | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤💋👩🏼" type="tts">kiss: woman, woman, medium-light skin tone</annotation> + <annotation cp="👩🏼❤💋👩🏽">couple | kiss | medium skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤💋👩🏽" type="tts">kiss: woman, woman, medium-light skin tone, medium skin tone</annotation> + <annotation cp="👩🏼❤💋👩🏾">couple | kiss | medium-dark skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤💋👩🏾" type="tts">kiss: woman, woman, medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏼❤💋👩🏿">couple | dark skin tone | kiss | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤💋👩🏿" type="tts">kiss: woman, woman, medium-light skin tone, dark skin tone</annotation> + <annotation cp="👩🏽❤💋👩🏻">couple | kiss | light skin tone | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤💋👩🏻" type="tts">kiss: woman, woman, medium skin tone, light skin tone</annotation> + <annotation cp="👩🏽❤💋👩🏼">couple | kiss | medium skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏽❤💋👩🏼" type="tts">kiss: woman, woman, medium skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏽❤💋👩🏽">couple | kiss | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤💋👩🏽" type="tts">kiss: woman, woman, medium skin tone</annotation> + <annotation cp="👩🏽❤💋👩🏾">couple | kiss | medium skin tone | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏽❤💋👩🏾" type="tts">kiss: woman, woman, medium skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏽❤💋👩🏿">couple | dark skin tone | kiss | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤💋👩🏿" type="tts">kiss: woman, woman, medium skin tone, dark skin tone</annotation> + <annotation cp="👩🏾❤💋👩🏻">couple | kiss | light skin tone | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤💋👩🏻" type="tts">kiss: woman, woman, medium-dark skin tone, light skin tone</annotation> + <annotation cp="👩🏾❤💋👩🏼">couple | kiss | medium-dark skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏾❤💋👩🏼" type="tts">kiss: woman, woman, medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏾❤💋👩🏽">couple | kiss | medium skin tone | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤💋👩🏽" type="tts">kiss: woman, woman, medium-dark skin tone, medium skin tone</annotation> + <annotation cp="👩🏾❤💋👩🏾">couple | kiss | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤💋👩🏾" type="tts">kiss: woman, woman, medium-dark skin tone</annotation> + <annotation cp="👩🏾❤💋👩🏿">couple | dark skin tone | kiss | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤💋👩🏿" type="tts">kiss: woman, woman, medium-dark skin tone, dark skin tone</annotation> + <annotation cp="👩🏿❤💋👩🏻">couple | dark skin tone | kiss | light skin tone | woman</annotation> + <annotation cp="👩🏿❤💋👩🏻" type="tts">kiss: woman, woman, dark skin tone, light skin tone</annotation> + <annotation cp="👩🏿❤💋👩🏼">couple | dark skin tone | kiss | medium-light skin tone | woman</annotation> + <annotation cp="👩🏿❤💋👩🏼" type="tts">kiss: woman, woman, dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏿❤💋👩🏽">couple | dark skin tone | kiss | medium skin tone | woman</annotation> + <annotation cp="👩🏿❤💋👩🏽" type="tts">kiss: woman, woman, dark skin tone, medium skin tone</annotation> + <annotation cp="👩🏿❤💋👩🏾">couple | dark skin tone | kiss | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏿❤💋👩🏾" type="tts">kiss: woman, woman, dark skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏿❤💋👩🏿">couple | dark skin tone | kiss | woman</annotation> + <annotation cp="👩🏿❤💋👩🏿" type="tts">kiss: woman, woman, dark skin tone</annotation> + <annotation cp="👩🏻❤👨🏻">couple | couple with heart | light skin tone | love | man | woman</annotation> + <annotation cp="👩🏻❤👨🏻" type="tts">couple with heart: woman, man, light skin tone</annotation> + <annotation cp="👩🏻❤👨🏼">couple | couple with heart | light skin tone | love | man | medium-light skin tone | woman</annotation> + <annotation cp="👩🏻❤👨🏼" type="tts">couple with heart: woman, man, light skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏻❤👨🏽">couple | couple with heart | light skin tone | love | man | medium skin tone | woman</annotation> + <annotation cp="👩🏻❤👨🏽" type="tts">couple with heart: woman, man, light skin tone, medium skin tone</annotation> + <annotation cp="👩🏻❤👨🏾">couple | couple with heart | light skin tone | love | man | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏻❤👨🏾" type="tts">couple with heart: woman, man, light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏻❤👨🏿">couple | couple with heart | dark skin tone | light skin tone | love | man | woman</annotation> + <annotation cp="👩🏻❤👨🏿" type="tts">couple with heart: woman, man, light skin tone, dark skin tone</annotation> + <annotation cp="👩🏼❤👨🏻">couple | couple with heart | light skin tone | love | man | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤👨🏻" type="tts">couple with heart: woman, man, medium-light skin tone, light skin tone</annotation> + <annotation cp="👩🏼❤👨🏼">couple | couple with heart | love | man | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤👨🏼" type="tts">couple with heart: woman, man, medium-light skin tone</annotation> + <annotation cp="👩🏼❤👨🏽">couple | couple with heart | love | man | medium skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤👨🏽" type="tts">couple with heart: woman, man, medium-light skin tone, medium skin tone</annotation> + <annotation cp="👩🏼❤👨🏾">couple | couple with heart | love | man | medium-dark skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤👨🏾" type="tts">couple with heart: woman, man, medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏼❤👨🏿">couple | couple with heart | dark skin tone | love | man | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤👨🏿" type="tts">couple with heart: woman, man, medium-light skin tone, dark skin tone</annotation> + <annotation cp="👩🏽❤👨🏻">couple | couple with heart | light skin tone | love | man | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤👨🏻" type="tts">couple with heart: woman, man, medium skin tone, light skin tone</annotation> + <annotation cp="👩🏽❤👨🏼">couple | couple with heart | love | man | medium skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏽❤👨🏼" type="tts">couple with heart: woman, man, medium skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏽❤👨🏽">couple | couple with heart | love | man | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤👨🏽" type="tts">couple with heart: woman, man, medium skin tone</annotation> + <annotation cp="👩🏽❤👨🏾">couple | couple with heart | love | man | medium skin tone | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏽❤👨🏾" type="tts">couple with heart: woman, man, medium skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏽❤👨🏿">couple | couple with heart | dark skin tone | love | man | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤👨🏿" type="tts">couple with heart: woman, man, medium skin tone, dark skin tone</annotation> + <annotation cp="👩🏾❤👨🏻">couple | couple with heart | light skin tone | love | man | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤👨🏻" type="tts">couple with heart: woman, man, medium-dark skin tone, light skin tone</annotation> + <annotation cp="👩🏾❤👨🏼">couple | couple with heart | love | man | medium-dark skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏾❤👨🏼" type="tts">couple with heart: woman, man, medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏾❤👨🏽">couple | couple with heart | love | man | medium skin tone | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤👨🏽" type="tts">couple with heart: woman, man, medium-dark skin tone, medium skin tone</annotation> + <annotation cp="👩🏾❤👨🏾">couple | couple with heart | love | man | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤👨🏾" type="tts">couple with heart: woman, man, medium-dark skin tone</annotation> + <annotation cp="👩🏾❤👨🏿">couple | couple with heart | dark skin tone | love | man | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤👨🏿" type="tts">couple with heart: woman, man, medium-dark skin tone, dark skin tone</annotation> + <annotation cp="👩🏿❤👨🏻">couple | couple with heart | dark skin tone | light skin tone | love | man | woman</annotation> + <annotation cp="👩🏿❤👨🏻" type="tts">couple with heart: woman, man, dark skin tone, light skin tone</annotation> + <annotation cp="👩🏿❤👨🏼">couple | couple with heart | dark skin tone | love | man | medium-light skin tone | woman</annotation> + <annotation cp="👩🏿❤👨🏼" type="tts">couple with heart: woman, man, dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏿❤👨🏽">couple | couple with heart | dark skin tone | love | man | medium skin tone | woman</annotation> + <annotation cp="👩🏿❤👨🏽" type="tts">couple with heart: woman, man, dark skin tone, medium skin tone</annotation> + <annotation cp="👩🏿❤👨🏾">couple | couple with heart | dark skin tone | love | man | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏿❤👨🏾" type="tts">couple with heart: woman, man, dark skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏿❤👨🏿">couple | couple with heart | dark skin tone | love | man | woman</annotation> + <annotation cp="👩🏿❤👨🏿" type="tts">couple with heart: woman, man, dark skin tone</annotation> + <annotation cp="👩🏻❤👩🏻">couple | couple with heart | light skin tone | love | woman</annotation> + <annotation cp="👩🏻❤👩🏻" type="tts">couple with heart: woman, woman, light skin tone</annotation> + <annotation cp="👩🏻❤👩🏼">couple | couple with heart | light skin tone | love | medium-light skin tone | woman</annotation> + <annotation cp="👩🏻❤👩🏼" type="tts">couple with heart: woman, woman, light skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏻❤👩🏽">couple | couple with heart | light skin tone | love | medium skin tone | woman</annotation> + <annotation cp="👩🏻❤👩🏽" type="tts">couple with heart: woman, woman, light skin tone, medium skin tone</annotation> + <annotation cp="👩🏻❤👩🏾">couple | couple with heart | light skin tone | love | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏻❤👩🏾" type="tts">couple with heart: woman, woman, light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏻❤👩🏿">couple | couple with heart | dark skin tone | light skin tone | love | woman</annotation> + <annotation cp="👩🏻❤👩🏿" type="tts">couple with heart: woman, woman, light skin tone, dark skin tone</annotation> + <annotation cp="👩🏼❤👩🏻">couple | couple with heart | light skin tone | love | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤👩🏻" type="tts">couple with heart: woman, woman, medium-light skin tone, light skin tone</annotation> + <annotation cp="👩🏼❤👩🏼">couple | couple with heart | love | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤👩🏼" type="tts">couple with heart: woman, woman, medium-light skin tone</annotation> + <annotation cp="👩🏼❤👩🏽">couple | couple with heart | love | medium skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤👩🏽" type="tts">couple with heart: woman, woman, medium-light skin tone, medium skin tone</annotation> + <annotation cp="👩🏼❤👩🏾">couple | couple with heart | love | medium-dark skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤👩🏾" type="tts">couple with heart: woman, woman, medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏼❤👩🏿">couple | couple with heart | dark skin tone | love | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼❤👩🏿" type="tts">couple with heart: woman, woman, medium-light skin tone, dark skin tone</annotation> + <annotation cp="👩🏽❤👩🏻">couple | couple with heart | light skin tone | love | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤👩🏻" type="tts">couple with heart: woman, woman, medium skin tone, light skin tone</annotation> + <annotation cp="👩🏽❤👩🏼">couple | couple with heart | love | medium skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏽❤👩🏼" type="tts">couple with heart: woman, woman, medium skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏽❤👩🏽">couple | couple with heart | love | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤👩🏽" type="tts">couple with heart: woman, woman, medium skin tone</annotation> + <annotation cp="👩🏽❤👩🏾">couple | couple with heart | love | medium skin tone | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏽❤👩🏾" type="tts">couple with heart: woman, woman, medium skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏽❤👩🏿">couple | couple with heart | dark skin tone | love | medium skin tone | woman</annotation> + <annotation cp="👩🏽❤👩🏿" type="tts">couple with heart: woman, woman, medium skin tone, dark skin tone</annotation> + <annotation cp="👩🏾❤👩🏻">couple | couple with heart | light skin tone | love | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤👩🏻" type="tts">couple with heart: woman, woman, medium-dark skin tone, light skin tone</annotation> + <annotation cp="👩🏾❤👩🏼">couple | couple with heart | love | medium-dark skin tone | medium-light skin tone | woman</annotation> + <annotation cp="👩🏾❤👩🏼" type="tts">couple with heart: woman, woman, medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏾❤👩🏽">couple | couple with heart | love | medium skin tone | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤👩🏽" type="tts">couple with heart: woman, woman, medium-dark skin tone, medium skin tone</annotation> + <annotation cp="👩🏾❤👩🏾">couple | couple with heart | love | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤👩🏾" type="tts">couple with heart: woman, woman, medium-dark skin tone</annotation> + <annotation cp="👩🏾❤👩🏿">couple | couple with heart | dark skin tone | love | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾❤👩🏿" type="tts">couple with heart: woman, woman, medium-dark skin tone, dark skin tone</annotation> + <annotation cp="👩🏿❤👩🏻">couple | couple with heart | dark skin tone | light skin tone | love | woman</annotation> + <annotation cp="👩🏿❤👩🏻" type="tts">couple with heart: woman, woman, dark skin tone, light skin tone</annotation> + <annotation cp="👩🏿❤👩🏼">couple | couple with heart | dark skin tone | love | medium-light skin tone | woman</annotation> + <annotation cp="👩🏿❤👩🏼" type="tts">couple with heart: woman, woman, dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏿❤👩🏽">couple | couple with heart | dark skin tone | love | medium skin tone | woman</annotation> + <annotation cp="👩🏿❤👩🏽" type="tts">couple with heart: woman, woman, dark skin tone, medium skin tone</annotation> + <annotation cp="👩🏿❤👩🏾">couple | couple with heart | dark skin tone | love | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏿❤👩🏾" type="tts">couple with heart: woman, woman, dark skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏿❤👩🏿">couple | couple with heart | dark skin tone | love | woman</annotation> + <annotation cp="👩🏿❤👩🏿" type="tts">couple with heart: woman, woman, dark skin tone</annotation> + <annotation cp="👩🦰">adult | red hair | woman</annotation> + <annotation cp="👩🦰" type="tts">woman: red hair</annotation> + <annotation cp="👩🏻🦰">adult | light skin tone | red hair | woman</annotation> + <annotation cp="👩🏻🦰" type="tts">woman: light skin tone, red hair</annotation> + <annotation cp="👩🏼🦰">adult | medium-light skin tone | red hair | woman</annotation> + <annotation cp="👩🏼🦰" type="tts">woman: medium-light skin tone, red hair</annotation> + <annotation cp="👩🏽🦰">adult | medium skin tone | red hair | woman</annotation> + <annotation cp="👩🏽🦰" type="tts">woman: medium skin tone, red hair</annotation> + <annotation cp="👩🏾🦰">adult | medium-dark skin tone | red hair | woman</annotation> + <annotation cp="👩🏾🦰" type="tts">woman: medium-dark skin tone, red hair</annotation> + <annotation cp="👩🏿🦰">adult | dark skin tone | red hair | woman</annotation> + <annotation cp="👩🏿🦰" type="tts">woman: dark skin tone, red hair</annotation> + <annotation cp="👩🦱">adult | curly hair | woman</annotation> + <annotation cp="👩🦱" type="tts">woman: curly hair</annotation> + <annotation cp="👩🏻🦱">adult | curly hair | light skin tone | woman</annotation> + <annotation cp="👩🏻🦱" type="tts">woman: light skin tone, curly hair</annotation> + <annotation cp="👩🏼🦱">adult | curly hair | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼🦱" type="tts">woman: medium-light skin tone, curly hair</annotation> + <annotation cp="👩🏽🦱">adult | curly hair | medium skin tone | woman</annotation> + <annotation cp="👩🏽🦱" type="tts">woman: medium skin tone, curly hair</annotation> + <annotation cp="👩🏾🦱">adult | curly hair | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾🦱" type="tts">woman: medium-dark skin tone, curly hair</annotation> + <annotation cp="👩🏿🦱">adult | curly hair | dark skin tone | woman</annotation> + <annotation cp="👩🏿🦱" type="tts">woman: dark skin tone, curly hair</annotation> + <annotation cp="👩🦳">adult | white hair | woman</annotation> + <annotation cp="👩🦳" type="tts">woman: white hair</annotation> + <annotation cp="👩🏻🦳">adult | light skin tone | white hair | woman</annotation> + <annotation cp="👩🏻🦳" type="tts">woman: light skin tone, white hair</annotation> + <annotation cp="👩🏼🦳">adult | medium-light skin tone | white hair | woman</annotation> + <annotation cp="👩🏼🦳" type="tts">woman: medium-light skin tone, white hair</annotation> + <annotation cp="👩🏽🦳">adult | medium skin tone | white hair | woman</annotation> + <annotation cp="👩🏽🦳" type="tts">woman: medium skin tone, white hair</annotation> + <annotation cp="👩🏾🦳">adult | medium-dark skin tone | white hair | woman</annotation> + <annotation cp="👩🏾🦳" type="tts">woman: medium-dark skin tone, white hair</annotation> + <annotation cp="👩🏿🦳">adult | dark skin tone | white hair | woman</annotation> + <annotation cp="👩🏿🦳" type="tts">woman: dark skin tone, white hair</annotation> + <annotation cp="👩🦲">adult | bald | woman</annotation> + <annotation cp="👩🦲" type="tts">woman: bald</annotation> + <annotation cp="👩🏻🦲">adult | bald | light skin tone | woman</annotation> + <annotation cp="👩🏻🦲" type="tts">woman: light skin tone, bald</annotation> + <annotation cp="👩🏼🦲">adult | bald | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼🦲" type="tts">woman: medium-light skin tone, bald</annotation> + <annotation cp="👩🏽🦲">adult | bald | medium skin tone | woman</annotation> + <annotation cp="👩🏽🦲" type="tts">woman: medium skin tone, bald</annotation> + <annotation cp="👩🏾🦲">adult | bald | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾🦲" type="tts">woman: medium-dark skin tone, bald</annotation> + <annotation cp="👩🏿🦲">adult | bald | dark skin tone | woman</annotation> + <annotation cp="👩🏿🦲" type="tts">woman: dark skin tone, bald</annotation> + <annotation cp="🧓🏻">adult | gender-neutral | light skin tone | old | older person | unspecified gender</annotation> + <annotation cp="🧓🏻" type="tts">older person: light skin tone</annotation> + <annotation cp="🧓🏼">adult | gender-neutral | medium-light skin tone | old | older person | unspecified gender</annotation> + <annotation cp="🧓🏼" type="tts">older person: medium-light skin tone</annotation> + <annotation cp="🧓🏽">adult | gender-neutral | medium skin tone | old | older person | unspecified gender</annotation> + <annotation cp="🧓🏽" type="tts">older person: medium skin tone</annotation> + <annotation cp="🧓🏾">adult | gender-neutral | medium-dark skin tone | old | older person | unspecified gender</annotation> + <annotation cp="🧓🏾" type="tts">older person: medium-dark skin tone</annotation> + <annotation cp="🧓🏿">adult | dark skin tone | gender-neutral | old | older person | unspecified gender</annotation> + <annotation cp="🧓🏿" type="tts">older person: dark skin tone</annotation> + <annotation cp="👴🏻">adult | light skin tone | man | old</annotation> + <annotation cp="👴🏻" type="tts">old man: light skin tone</annotation> + <annotation cp="👴🏼">adult | man | medium-light skin tone | old</annotation> + <annotation cp="👴🏼" type="tts">old man: medium-light skin tone</annotation> + <annotation cp="👴🏽">adult | man | medium skin tone | old</annotation> + <annotation cp="👴🏽" type="tts">old man: medium skin tone</annotation> + <annotation cp="👴🏾">adult | man | medium-dark skin tone | old</annotation> + <annotation cp="👴🏾" type="tts">old man: medium-dark skin tone</annotation> + <annotation cp="👴🏿">adult | dark skin tone | man | old</annotation> + <annotation cp="👴🏿" type="tts">old man: dark skin tone</annotation> + <annotation cp="👵🏻">adult | light skin tone | old | woman</annotation> + <annotation cp="👵🏻" type="tts">old woman: light skin tone</annotation> + <annotation cp="👵🏼">adult | medium-light skin tone | old | woman</annotation> + <annotation cp="👵🏼" type="tts">old woman: medium-light skin tone</annotation> + <annotation cp="👵🏽">adult | medium skin tone | old | woman</annotation> + <annotation cp="👵🏽" type="tts">old woman: medium skin tone</annotation> + <annotation cp="👵🏾">adult | medium-dark skin tone | old | woman</annotation> + <annotation cp="👵🏾" type="tts">old woman: medium-dark skin tone</annotation> + <annotation cp="👵🏿">adult | dark skin tone | old | woman</annotation> + <annotation cp="👵🏿" type="tts">old woman: dark skin tone</annotation> + <annotation cp="🙍🏻">frown | gesture | light skin tone | person frowning</annotation> + <annotation cp="🙍🏻" type="tts">person frowning: light skin tone</annotation> + <annotation cp="🙍🏼">frown | gesture | medium-light skin tone | person frowning</annotation> + <annotation cp="🙍🏼" type="tts">person frowning: medium-light skin tone</annotation> + <annotation cp="🙍🏽">frown | gesture | medium skin tone | person frowning</annotation> + <annotation cp="🙍🏽" type="tts">person frowning: medium skin tone</annotation> + <annotation cp="🙍🏾">frown | gesture | medium-dark skin tone | person frowning</annotation> + <annotation cp="🙍🏾" type="tts">person frowning: medium-dark skin tone</annotation> + <annotation cp="🙍🏿">dark skin tone | frown | gesture | person frowning</annotation> + <annotation cp="🙍🏿" type="tts">person frowning: dark skin tone</annotation> + <annotation cp="🙍🏻♂">frowning | gesture | light skin tone | man</annotation> + <annotation cp="🙍🏻♂" type="tts">man frowning: light skin tone</annotation> + <annotation cp="🙍🏼♂">frowning | gesture | man | medium-light skin tone</annotation> + <annotation cp="🙍🏼♂" type="tts">man frowning: medium-light skin tone</annotation> + <annotation cp="🙍🏽♂">frowning | gesture | man | medium skin tone</annotation> + <annotation cp="🙍🏽♂" type="tts">man frowning: medium skin tone</annotation> + <annotation cp="🙍🏾♂">frowning | gesture | man | medium-dark skin tone</annotation> + <annotation cp="🙍🏾♂" type="tts">man frowning: medium-dark skin tone</annotation> + <annotation cp="🙍🏿♂">dark skin tone | frowning | gesture | man</annotation> + <annotation cp="🙍🏿♂" type="tts">man frowning: dark skin tone</annotation> + <annotation cp="🙍🏻♀">frowning | gesture | light skin tone | woman</annotation> + <annotation cp="🙍🏻♀" type="tts">woman frowning: light skin tone</annotation> + <annotation cp="🙍🏼♀">frowning | gesture | medium-light skin tone | woman</annotation> + <annotation cp="🙍🏼♀" type="tts">woman frowning: medium-light skin tone</annotation> + <annotation cp="🙍🏽♀">frowning | gesture | medium skin tone | woman</annotation> + <annotation cp="🙍🏽♀" type="tts">woman frowning: medium skin tone</annotation> + <annotation cp="🙍🏾♀">frowning | gesture | medium-dark skin tone | woman</annotation> + <annotation cp="🙍🏾♀" type="tts">woman frowning: medium-dark skin tone</annotation> + <annotation cp="🙍🏿♀">dark skin tone | frowning | gesture | woman</annotation> + <annotation cp="🙍🏿♀" type="tts">woman frowning: dark skin tone</annotation> + <annotation cp="🙎🏻">gesture | light skin tone | person pouting | pouting</annotation> + <annotation cp="🙎🏻" type="tts">person pouting: light skin tone</annotation> + <annotation cp="🙎🏼">gesture | medium-light skin tone | person pouting | pouting</annotation> + <annotation cp="🙎🏼" type="tts">person pouting: medium-light skin tone</annotation> + <annotation cp="🙎🏽">gesture | medium skin tone | person pouting | pouting</annotation> + <annotation cp="🙎🏽" type="tts">person pouting: medium skin tone</annotation> + <annotation cp="🙎🏾">gesture | medium-dark skin tone | person pouting | pouting</annotation> + <annotation cp="🙎🏾" type="tts">person pouting: medium-dark skin tone</annotation> + <annotation cp="🙎🏿">dark skin tone | gesture | person pouting | pouting</annotation> + <annotation cp="🙎🏿" type="tts">person pouting: dark skin tone</annotation> + <annotation cp="🙎🏻♂">gesture | light skin tone | man | pouting</annotation> + <annotation cp="🙎🏻♂" type="tts">man pouting: light skin tone</annotation> + <annotation cp="🙎🏼♂">gesture | man | medium-light skin tone | pouting</annotation> + <annotation cp="🙎🏼♂" type="tts">man pouting: medium-light skin tone</annotation> + <annotation cp="🙎🏽♂">gesture | man | medium skin tone | pouting</annotation> + <annotation cp="🙎🏽♂" type="tts">man pouting: medium skin tone</annotation> + <annotation cp="🙎🏾♂">gesture | man | medium-dark skin tone | pouting</annotation> + <annotation cp="🙎🏾♂" type="tts">man pouting: medium-dark skin tone</annotation> + <annotation cp="🙎🏿♂">dark skin tone | gesture | man | pouting</annotation> + <annotation cp="🙎🏿♂" type="tts">man pouting: dark skin tone</annotation> + <annotation cp="🙎🏻♀">gesture | light skin tone | pouting | woman</annotation> + <annotation cp="🙎🏻♀" type="tts">woman pouting: light skin tone</annotation> + <annotation cp="🙎🏼♀">gesture | medium-light skin tone | pouting | woman</annotation> + <annotation cp="🙎🏼♀" type="tts">woman pouting: medium-light skin tone</annotation> + <annotation cp="🙎🏽♀">gesture | medium skin tone | pouting | woman</annotation> + <annotation cp="🙎🏽♀" type="tts">woman pouting: medium skin tone</annotation> + <annotation cp="🙎🏾♀">gesture | medium-dark skin tone | pouting | woman</annotation> + <annotation cp="🙎🏾♀" type="tts">woman pouting: medium-dark skin tone</annotation> + <annotation cp="🙎🏿♀">dark skin tone | gesture | pouting | woman</annotation> + <annotation cp="🙎🏿♀" type="tts">woman pouting: dark skin tone</annotation> + <annotation cp="🙅🏻">forbidden | gesture | hand | light skin tone | person gesturing NO | prohibited</annotation> + <annotation cp="🙅🏻" type="tts">person gesturing NO: light skin tone</annotation> + <annotation cp="🙅🏼">forbidden | gesture | hand | medium-light skin tone | person gesturing NO | prohibited</annotation> + <annotation cp="🙅🏼" type="tts">person gesturing NO: medium-light skin tone</annotation> + <annotation cp="🙅🏽">forbidden | gesture | hand | medium skin tone | person gesturing NO | prohibited</annotation> + <annotation cp="🙅🏽" type="tts">person gesturing NO: medium skin tone</annotation> + <annotation cp="🙅🏾">forbidden | gesture | hand | medium-dark skin tone | person gesturing NO | prohibited</annotation> + <annotation cp="🙅🏾" type="tts">person gesturing NO: medium-dark skin tone</annotation> + <annotation cp="🙅🏿">dark skin tone | forbidden | gesture | hand | person gesturing NO | prohibited</annotation> + <annotation cp="🙅🏿" type="tts">person gesturing NO: dark skin tone</annotation> + <annotation cp="🙅🏻♂">forbidden | gesture | hand | light skin tone | man | man gesturing NO | prohibited</annotation> + <annotation cp="🙅🏻♂" type="tts">man gesturing NO: light skin tone</annotation> + <annotation cp="🙅🏼♂">forbidden | gesture | hand | man | man gesturing NO | medium-light skin tone | prohibited</annotation> + <annotation cp="🙅🏼♂" type="tts">man gesturing NO: medium-light skin tone</annotation> + <annotation cp="🙅🏽♂">forbidden | gesture | hand | man | man gesturing NO | medium skin tone | prohibited</annotation> + <annotation cp="🙅🏽♂" type="tts">man gesturing NO: medium skin tone</annotation> + <annotation cp="🙅🏾♂">forbidden | gesture | hand | man | man gesturing NO | medium-dark skin tone | prohibited</annotation> + <annotation cp="🙅🏾♂" type="tts">man gesturing NO: medium-dark skin tone</annotation> + <annotation cp="🙅🏿♂">dark skin tone | forbidden | gesture | hand | man | man gesturing NO | prohibited</annotation> + <annotation cp="🙅🏿♂" type="tts">man gesturing NO: dark skin tone</annotation> + <annotation cp="🙅🏻♀">forbidden | gesture | hand | light skin tone | prohibited | woman | woman gesturing NO</annotation> + <annotation cp="🙅🏻♀" type="tts">woman gesturing NO: light skin tone</annotation> + <annotation cp="🙅🏼♀">forbidden | gesture | hand | medium-light skin tone | prohibited | woman | woman gesturing NO</annotation> + <annotation cp="🙅🏼♀" type="tts">woman gesturing NO: medium-light skin tone</annotation> + <annotation cp="🙅🏽♀">forbidden | gesture | hand | medium skin tone | prohibited | woman | woman gesturing NO</annotation> + <annotation cp="🙅🏽♀" type="tts">woman gesturing NO: medium skin tone</annotation> + <annotation cp="🙅🏾♀">forbidden | gesture | hand | medium-dark skin tone | prohibited | woman | woman gesturing NO</annotation> + <annotation cp="🙅🏾♀" type="tts">woman gesturing NO: medium-dark skin tone</annotation> + <annotation cp="🙅🏿♀">dark skin tone | forbidden | gesture | hand | prohibited | woman | woman gesturing NO</annotation> + <annotation cp="🙅🏿♀" type="tts">woman gesturing NO: dark skin tone</annotation> + <annotation cp="🙆🏻">gesture | hand | light skin tone | OK | person gesturing OK</annotation> + <annotation cp="🙆🏻" type="tts">person gesturing OK: light skin tone</annotation> + <annotation cp="🙆🏼">gesture | hand | medium-light skin tone | OK | person gesturing OK</annotation> + <annotation cp="🙆🏼" type="tts">person gesturing OK: medium-light skin tone</annotation> + <annotation cp="🙆🏽">gesture | hand | medium skin tone | OK | person gesturing OK</annotation> + <annotation cp="🙆🏽" type="tts">person gesturing OK: medium skin tone</annotation> + <annotation cp="🙆🏾">gesture | hand | medium-dark skin tone | OK | person gesturing OK</annotation> + <annotation cp="🙆🏾" type="tts">person gesturing OK: medium-dark skin tone</annotation> + <annotation cp="🙆🏿">dark skin tone | gesture | hand | OK | person gesturing OK</annotation> + <annotation cp="🙆🏿" type="tts">person gesturing OK: dark skin tone</annotation> + <annotation cp="🙆🏻♂">gesture | hand | light skin tone | man | man gesturing OK | OK</annotation> + <annotation cp="🙆🏻♂" type="tts">man gesturing OK: light skin tone</annotation> + <annotation cp="🙆🏼♂">gesture | hand | man | man gesturing OK | medium-light skin tone | OK</annotation> + <annotation cp="🙆🏼♂" type="tts">man gesturing OK: medium-light skin tone</annotation> + <annotation cp="🙆🏽♂">gesture | hand | man | man gesturing OK | medium skin tone | OK</annotation> + <annotation cp="🙆🏽♂" type="tts">man gesturing OK: medium skin tone</annotation> + <annotation cp="🙆🏾♂">gesture | hand | man | man gesturing OK | medium-dark skin tone | OK</annotation> + <annotation cp="🙆🏾♂" type="tts">man gesturing OK: medium-dark skin tone</annotation> + <annotation cp="🙆🏿♂">dark skin tone | gesture | hand | man | man gesturing OK | OK</annotation> + <annotation cp="🙆🏿♂" type="tts">man gesturing OK: dark skin tone</annotation> + <annotation cp="🙆🏻♀">gesture | hand | light skin tone | OK | woman | woman gesturing OK</annotation> + <annotation cp="🙆🏻♀" type="tts">woman gesturing OK: light skin tone</annotation> + <annotation cp="🙆🏼♀">gesture | hand | medium-light skin tone | OK | woman | woman gesturing OK</annotation> + <annotation cp="🙆🏼♀" type="tts">woman gesturing OK: medium-light skin tone</annotation> + <annotation cp="🙆🏽♀">gesture | hand | medium skin tone | OK | woman | woman gesturing OK</annotation> + <annotation cp="🙆🏽♀" type="tts">woman gesturing OK: medium skin tone</annotation> + <annotation cp="🙆🏾♀">gesture | hand | medium-dark skin tone | OK | woman | woman gesturing OK</annotation> + <annotation cp="🙆🏾♀" type="tts">woman gesturing OK: medium-dark skin tone</annotation> + <annotation cp="🙆🏿♀">dark skin tone | gesture | hand | OK | woman | woman gesturing OK</annotation> + <annotation cp="🙆🏿♀" type="tts">woman gesturing OK: dark skin tone</annotation> + <annotation cp="💁🏻">hand | help | information | light skin tone | person tipping hand | sassy | tipping</annotation> + <annotation cp="💁🏻" type="tts">person tipping hand: light skin tone</annotation> + <annotation cp="💁🏼">hand | help | information | medium-light skin tone | person tipping hand | sassy | tipping</annotation> + <annotation cp="💁🏼" type="tts">person tipping hand: medium-light skin tone</annotation> + <annotation cp="💁🏽">hand | help | information | medium skin tone | person tipping hand | sassy | tipping</annotation> + <annotation cp="💁🏽" type="tts">person tipping hand: medium skin tone</annotation> + <annotation cp="💁🏾">hand | help | information | medium-dark skin tone | person tipping hand | sassy | tipping</annotation> + <annotation cp="💁🏾" type="tts">person tipping hand: medium-dark skin tone</annotation> + <annotation cp="💁🏿">dark skin tone | hand | help | information | person tipping hand | sassy | tipping</annotation> + <annotation cp="💁🏿" type="tts">person tipping hand: dark skin tone</annotation> + <annotation cp="💁🏻♂">light skin tone | man | man tipping hand | sassy | tipping hand</annotation> + <annotation cp="💁🏻♂" type="tts">man tipping hand: light skin tone</annotation> + <annotation cp="💁🏼♂">man | man tipping hand | medium-light skin tone | sassy | tipping hand</annotation> + <annotation cp="💁🏼♂" type="tts">man tipping hand: medium-light skin tone</annotation> + <annotation cp="💁🏽♂">man | man tipping hand | medium skin tone | sassy | tipping hand</annotation> + <annotation cp="💁🏽♂" type="tts">man tipping hand: medium skin tone</annotation> + <annotation cp="💁🏾♂">man | man tipping hand | medium-dark skin tone | sassy | tipping hand</annotation> + <annotation cp="💁🏾♂" type="tts">man tipping hand: medium-dark skin tone</annotation> + <annotation cp="💁🏿♂">dark skin tone | man | man tipping hand | sassy | tipping hand</annotation> + <annotation cp="💁🏿♂" type="tts">man tipping hand: dark skin tone</annotation> + <annotation cp="💁🏻♀">light skin tone | sassy | tipping hand | woman | woman tipping hand</annotation> + <annotation cp="💁🏻♀" type="tts">woman tipping hand: light skin tone</annotation> + <annotation cp="💁🏼♀">medium-light skin tone | sassy | tipping hand | woman | woman tipping hand</annotation> + <annotation cp="💁🏼♀" type="tts">woman tipping hand: medium-light skin tone</annotation> + <annotation cp="💁🏽♀">medium skin tone | sassy | tipping hand | woman | woman tipping hand</annotation> + <annotation cp="💁🏽♀" type="tts">woman tipping hand: medium skin tone</annotation> + <annotation cp="💁🏾♀">medium-dark skin tone | sassy | tipping hand | woman | woman tipping hand</annotation> + <annotation cp="💁🏾♀" type="tts">woman tipping hand: medium-dark skin tone</annotation> + <annotation cp="💁🏿♀">dark skin tone | sassy | tipping hand | woman | woman tipping hand</annotation> + <annotation cp="💁🏿♀" type="tts">woman tipping hand: dark skin tone</annotation> + <annotation cp="🙋🏻">gesture | hand | happy | light skin tone | person raising hand | raised</annotation> + <annotation cp="🙋🏻" type="tts">person raising hand: light skin tone</annotation> + <annotation cp="🙋🏼">gesture | hand | happy | medium-light skin tone | person raising hand | raised</annotation> + <annotation cp="🙋🏼" type="tts">person raising hand: medium-light skin tone</annotation> + <annotation cp="🙋🏽">gesture | hand | happy | medium skin tone | person raising hand | raised</annotation> + <annotation cp="🙋🏽" type="tts">person raising hand: medium skin tone</annotation> + <annotation cp="🙋🏾">gesture | hand | happy | medium-dark skin tone | person raising hand | raised</annotation> + <annotation cp="🙋🏾" type="tts">person raising hand: medium-dark skin tone</annotation> + <annotation cp="🙋🏿">dark skin tone | gesture | hand | happy | person raising hand | raised</annotation> + <annotation cp="🙋🏿" type="tts">person raising hand: dark skin tone</annotation> + <annotation cp="🙋🏻♂">gesture | light skin tone | man | man raising hand | raising hand</annotation> + <annotation cp="🙋🏻♂" type="tts">man raising hand: light skin tone</annotation> + <annotation cp="🙋🏼♂">gesture | man | man raising hand | medium-light skin tone | raising hand</annotation> + <annotation cp="🙋🏼♂" type="tts">man raising hand: medium-light skin tone</annotation> + <annotation cp="🙋🏽♂">gesture | man | man raising hand | medium skin tone | raising hand</annotation> + <annotation cp="🙋🏽♂" type="tts">man raising hand: medium skin tone</annotation> + <annotation cp="🙋🏾♂">gesture | man | man raising hand | medium-dark skin tone | raising hand</annotation> + <annotation cp="🙋🏾♂" type="tts">man raising hand: medium-dark skin tone</annotation> + <annotation cp="🙋🏿♂">dark skin tone | gesture | man | man raising hand | raising hand</annotation> + <annotation cp="🙋🏿♂" type="tts">man raising hand: dark skin tone</annotation> + <annotation cp="🙋🏻♀">gesture | light skin tone | raising hand | woman | woman raising hand</annotation> + <annotation cp="🙋🏻♀" type="tts">woman raising hand: light skin tone</annotation> + <annotation cp="🙋🏼♀">gesture | medium-light skin tone | raising hand | woman | woman raising hand</annotation> + <annotation cp="🙋🏼♀" type="tts">woman raising hand: medium-light skin tone</annotation> + <annotation cp="🙋🏽♀">gesture | medium skin tone | raising hand | woman | woman raising hand</annotation> + <annotation cp="🙋🏽♀" type="tts">woman raising hand: medium skin tone</annotation> + <annotation cp="🙋🏾♀">gesture | medium-dark skin tone | raising hand | woman | woman raising hand</annotation> + <annotation cp="🙋🏾♀" type="tts">woman raising hand: medium-dark skin tone</annotation> + <annotation cp="🙋🏿♀">dark skin tone | gesture | raising hand | woman | woman raising hand</annotation> + <annotation cp="🙋🏿♀" type="tts">woman raising hand: dark skin tone</annotation> + <annotation cp="🧏🏻">accessibility | deaf | deaf person | ear | hear | light skin tone</annotation> + <annotation cp="🧏🏻" type="tts">deaf person: light skin tone</annotation> + <annotation cp="🧏🏼">accessibility | deaf | deaf person | ear | hear | medium-light skin tone</annotation> + <annotation cp="🧏🏼" type="tts">deaf person: medium-light skin tone</annotation> + <annotation cp="🧏🏽">accessibility | deaf | deaf person | ear | hear | medium skin tone</annotation> + <annotation cp="🧏🏽" type="tts">deaf person: medium skin tone</annotation> + <annotation cp="🧏🏾">accessibility | deaf | deaf person | ear | hear | medium-dark skin tone</annotation> + <annotation cp="🧏🏾" type="tts">deaf person: medium-dark skin tone</annotation> + <annotation cp="🧏🏿">accessibility | dark skin tone | deaf | deaf person | ear | hear</annotation> + <annotation cp="🧏🏿" type="tts">deaf person: dark skin tone</annotation> + <annotation cp="🧏🏻♂">deaf | light skin tone | man</annotation> + <annotation cp="🧏🏻♂" type="tts">deaf man: light skin tone</annotation> + <annotation cp="🧏🏼♂">deaf | man | medium-light skin tone</annotation> + <annotation cp="🧏🏼♂" type="tts">deaf man: medium-light skin tone</annotation> + <annotation cp="🧏🏽♂">deaf | man | medium skin tone</annotation> + <annotation cp="🧏🏽♂" type="tts">deaf man: medium skin tone</annotation> + <annotation cp="🧏🏾♂">deaf | man | medium-dark skin tone</annotation> + <annotation cp="🧏🏾♂" type="tts">deaf man: medium-dark skin tone</annotation> + <annotation cp="🧏🏿♂">dark skin tone | deaf | man</annotation> + <annotation cp="🧏🏿♂" type="tts">deaf man: dark skin tone</annotation> + <annotation cp="🧏🏻♀">deaf | light skin tone | woman</annotation> + <annotation cp="🧏🏻♀" type="tts">deaf woman: light skin tone</annotation> + <annotation cp="🧏🏼♀">deaf | medium-light skin tone | woman</annotation> + <annotation cp="🧏🏼♀" type="tts">deaf woman: medium-light skin tone</annotation> + <annotation cp="🧏🏽♀">deaf | medium skin tone | woman</annotation> + <annotation cp="🧏🏽♀" type="tts">deaf woman: medium skin tone</annotation> + <annotation cp="🧏🏾♀">deaf | medium-dark skin tone | woman</annotation> + <annotation cp="🧏🏾♀" type="tts">deaf woman: medium-dark skin tone</annotation> + <annotation cp="🧏🏿♀">dark skin tone | deaf | woman</annotation> + <annotation cp="🧏🏿♀" type="tts">deaf woman: dark skin tone</annotation> + <annotation cp="🙇🏻">apology | bow | gesture | light skin tone | person bowing | sorry</annotation> + <annotation cp="🙇🏻" type="tts">person bowing: light skin tone</annotation> + <annotation cp="🙇🏼">apology | bow | gesture | medium-light skin tone | person bowing | sorry</annotation> + <annotation cp="🙇🏼" type="tts">person bowing: medium-light skin tone</annotation> + <annotation cp="🙇🏽">apology | bow | gesture | medium skin tone | person bowing | sorry</annotation> + <annotation cp="🙇🏽" type="tts">person bowing: medium skin tone</annotation> + <annotation cp="🙇🏾">apology | bow | gesture | medium-dark skin tone | person bowing | sorry</annotation> + <annotation cp="🙇🏾" type="tts">person bowing: medium-dark skin tone</annotation> + <annotation cp="🙇🏿">apology | bow | dark skin tone | gesture | person bowing | sorry</annotation> + <annotation cp="🙇🏿" type="tts">person bowing: dark skin tone</annotation> + <annotation cp="🙇🏻♂">apology | bowing | favor | gesture | light skin tone | man | sorry</annotation> + <annotation cp="🙇🏻♂" type="tts">man bowing: light skin tone</annotation> + <annotation cp="🙇🏼♂">apology | bowing | favor | gesture | man | medium-light skin tone | sorry</annotation> + <annotation cp="🙇🏼♂" type="tts">man bowing: medium-light skin tone</annotation> + <annotation cp="🙇🏽♂">apology | bowing | favor | gesture | man | medium skin tone | sorry</annotation> + <annotation cp="🙇🏽♂" type="tts">man bowing: medium skin tone</annotation> + <annotation cp="🙇🏾♂">apology | bowing | favor | gesture | man | medium-dark skin tone | sorry</annotation> + <annotation cp="🙇🏾♂" type="tts">man bowing: medium-dark skin tone</annotation> + <annotation cp="🙇🏿♂">apology | bowing | dark skin tone | favor | gesture | man | sorry</annotation> + <annotation cp="🙇🏿♂" type="tts">man bowing: dark skin tone</annotation> + <annotation cp="🙇🏻♀">apology | bowing | favor | gesture | light skin tone | sorry | woman</annotation> + <annotation cp="🙇🏻♀" type="tts">woman bowing: light skin tone</annotation> + <annotation cp="🙇🏼♀">apology | bowing | favor | gesture | medium-light skin tone | sorry | woman</annotation> + <annotation cp="🙇🏼♀" type="tts">woman bowing: medium-light skin tone</annotation> + <annotation cp="🙇🏽♀">apology | bowing | favor | gesture | medium skin tone | sorry | woman</annotation> + <annotation cp="🙇🏽♀" type="tts">woman bowing: medium skin tone</annotation> + <annotation cp="🙇🏾♀">apology | bowing | favor | gesture | medium-dark skin tone | sorry | woman</annotation> + <annotation cp="🙇🏾♀" type="tts">woman bowing: medium-dark skin tone</annotation> + <annotation cp="🙇🏿♀">apology | bowing | dark skin tone | favor | gesture | sorry | woman</annotation> + <annotation cp="🙇🏿♀" type="tts">woman bowing: dark skin tone</annotation> + <annotation cp="🤦🏻">disbelief | exasperation | face | light skin tone | palm | person facepalming</annotation> + <annotation cp="🤦🏻" type="tts">person facepalming: light skin tone</annotation> + <annotation cp="🤦🏼">disbelief | exasperation | face | medium-light skin tone | palm | person facepalming</annotation> + <annotation cp="🤦🏼" type="tts">person facepalming: medium-light skin tone</annotation> + <annotation cp="🤦🏽">disbelief | exasperation | face | medium skin tone | palm | person facepalming</annotation> + <annotation cp="🤦🏽" type="tts">person facepalming: medium skin tone</annotation> + <annotation cp="🤦🏾">disbelief | exasperation | face | medium-dark skin tone | palm | person facepalming</annotation> + <annotation cp="🤦🏾" type="tts">person facepalming: medium-dark skin tone</annotation> + <annotation cp="🤦🏿">dark skin tone | disbelief | exasperation | face | palm | person facepalming</annotation> + <annotation cp="🤦🏿" type="tts">person facepalming: dark skin tone</annotation> + <annotation cp="🤦🏻♂">disbelief | exasperation | facepalm | light skin tone | man | man facepalming</annotation> + <annotation cp="🤦🏻♂" type="tts">man facepalming: light skin tone</annotation> + <annotation cp="🤦🏼♂">disbelief | exasperation | facepalm | man | man facepalming | medium-light skin tone</annotation> + <annotation cp="🤦🏼♂" type="tts">man facepalming: medium-light skin tone</annotation> + <annotation cp="🤦🏽♂">disbelief | exasperation | facepalm | man | man facepalming | medium skin tone</annotation> + <annotation cp="🤦🏽♂" type="tts">man facepalming: medium skin tone</annotation> + <annotation cp="🤦🏾♂">disbelief | exasperation | facepalm | man | man facepalming | medium-dark skin tone</annotation> + <annotation cp="🤦🏾♂" type="tts">man facepalming: medium-dark skin tone</annotation> + <annotation cp="🤦🏿♂">dark skin tone | disbelief | exasperation | facepalm | man | man facepalming</annotation> + <annotation cp="🤦🏿♂" type="tts">man facepalming: dark skin tone</annotation> + <annotation cp="🤦🏻♀">disbelief | exasperation | facepalm | light skin tone | woman | woman facepalming</annotation> + <annotation cp="🤦🏻♀" type="tts">woman facepalming: light skin tone</annotation> + <annotation cp="🤦🏼♀">disbelief | exasperation | facepalm | medium-light skin tone | woman | woman facepalming</annotation> + <annotation cp="🤦🏼♀" type="tts">woman facepalming: medium-light skin tone</annotation> + <annotation cp="🤦🏽♀">disbelief | exasperation | facepalm | medium skin tone | woman | woman facepalming</annotation> + <annotation cp="🤦🏽♀" type="tts">woman facepalming: medium skin tone</annotation> + <annotation cp="🤦🏾♀">disbelief | exasperation | facepalm | medium-dark skin tone | woman | woman facepalming</annotation> + <annotation cp="🤦🏾♀" type="tts">woman facepalming: medium-dark skin tone</annotation> + <annotation cp="🤦🏿♀">dark skin tone | disbelief | exasperation | facepalm | woman | woman facepalming</annotation> + <annotation cp="🤦🏿♀" type="tts">woman facepalming: dark skin tone</annotation> + <annotation cp="🤷🏻">doubt | ignorance | indifference | light skin tone | person shrugging | shrug</annotation> + <annotation cp="🤷🏻" type="tts">person shrugging: light skin tone</annotation> + <annotation cp="🤷🏼">doubt | ignorance | indifference | medium-light skin tone | person shrugging | shrug</annotation> + <annotation cp="🤷🏼" type="tts">person shrugging: medium-light skin tone</annotation> + <annotation cp="🤷🏽">doubt | ignorance | indifference | medium skin tone | person shrugging | shrug</annotation> + <annotation cp="🤷🏽" type="tts">person shrugging: medium skin tone</annotation> + <annotation cp="🤷🏾">doubt | ignorance | indifference | medium-dark skin tone | person shrugging | shrug</annotation> + <annotation cp="🤷🏾" type="tts">person shrugging: medium-dark skin tone</annotation> + <annotation cp="🤷🏿">dark skin tone | doubt | ignorance | indifference | person shrugging | shrug</annotation> + <annotation cp="🤷🏿" type="tts">person shrugging: dark skin tone</annotation> + <annotation cp="🤷🏻♂">doubt | ignorance | indifference | light skin tone | man | man shrugging | shrug</annotation> + <annotation cp="🤷🏻♂" type="tts">man shrugging: light skin tone</annotation> + <annotation cp="🤷🏼♂">doubt | ignorance | indifference | man | man shrugging | medium-light skin tone | shrug</annotation> + <annotation cp="🤷🏼♂" type="tts">man shrugging: medium-light skin tone</annotation> + <annotation cp="🤷🏽♂">doubt | ignorance | indifference | man | man shrugging | medium skin tone | shrug</annotation> + <annotation cp="🤷🏽♂" type="tts">man shrugging: medium skin tone</annotation> + <annotation cp="🤷🏾♂">doubt | ignorance | indifference | man | man shrugging | medium-dark skin tone | shrug</annotation> + <annotation cp="🤷🏾♂" type="tts">man shrugging: medium-dark skin tone</annotation> + <annotation cp="🤷🏿♂">dark skin tone | doubt | ignorance | indifference | man | man shrugging | shrug</annotation> + <annotation cp="🤷🏿♂" type="tts">man shrugging: dark skin tone</annotation> + <annotation cp="🤷🏻♀">doubt | ignorance | indifference | light skin tone | shrug | woman | woman shrugging</annotation> + <annotation cp="🤷🏻♀" type="tts">woman shrugging: light skin tone</annotation> + <annotation cp="🤷🏼♀">doubt | ignorance | indifference | medium-light skin tone | shrug | woman | woman shrugging</annotation> + <annotation cp="🤷🏼♀" type="tts">woman shrugging: medium-light skin tone</annotation> + <annotation cp="🤷🏽♀">doubt | ignorance | indifference | medium skin tone | shrug | woman | woman shrugging</annotation> + <annotation cp="🤷🏽♀" type="tts">woman shrugging: medium skin tone</annotation> + <annotation cp="🤷🏾♀">doubt | ignorance | indifference | medium-dark skin tone | shrug | woman | woman shrugging</annotation> + <annotation cp="🤷🏾♀" type="tts">woman shrugging: medium-dark skin tone</annotation> + <annotation cp="🤷🏿♀">dark skin tone | doubt | ignorance | indifference | shrug | woman | woman shrugging</annotation> + <annotation cp="🤷🏿♀" type="tts">woman shrugging: dark skin tone</annotation> + <annotation cp="🧑🏻⚕">doctor | health worker | healthcare | light skin tone | nurse | therapist</annotation> + <annotation cp="🧑🏻⚕" type="tts">health worker: light skin tone</annotation> + <annotation cp="🧑🏼⚕">doctor | health worker | healthcare | medium-light skin tone | nurse | therapist</annotation> + <annotation cp="🧑🏼⚕" type="tts">health worker: medium-light skin tone</annotation> + <annotation cp="🧑🏽⚕">doctor | health worker | healthcare | medium skin tone | nurse | therapist</annotation> + <annotation cp="🧑🏽⚕" type="tts">health worker: medium skin tone</annotation> + <annotation cp="🧑🏾⚕">doctor | health worker | healthcare | medium-dark skin tone | nurse | therapist</annotation> + <annotation cp="🧑🏾⚕" type="tts">health worker: medium-dark skin tone</annotation> + <annotation cp="🧑🏿⚕">dark skin tone | doctor | health worker | healthcare | nurse | therapist</annotation> + <annotation cp="🧑🏿⚕" type="tts">health worker: dark skin tone</annotation> + <annotation cp="👨🏻⚕">doctor | healthcare | light skin tone | man | man health worker | nurse | therapist</annotation> + <annotation cp="👨🏻⚕" type="tts">man health worker: light skin tone</annotation> + <annotation cp="👨🏼⚕">doctor | healthcare | man | man health worker | medium-light skin tone | nurse | therapist</annotation> + <annotation cp="👨🏼⚕" type="tts">man health worker: medium-light skin tone</annotation> + <annotation cp="👨🏽⚕">doctor | healthcare | man | man health worker | medium skin tone | nurse | therapist</annotation> + <annotation cp="👨🏽⚕" type="tts">man health worker: medium skin tone</annotation> + <annotation cp="👨🏾⚕">doctor | healthcare | man | man health worker | medium-dark skin tone | nurse | therapist</annotation> + <annotation cp="👨🏾⚕" type="tts">man health worker: medium-dark skin tone</annotation> + <annotation cp="👨🏿⚕">dark skin tone | doctor | healthcare | man | man health worker | nurse | therapist</annotation> + <annotation cp="👨🏿⚕" type="tts">man health worker: dark skin tone</annotation> + <annotation cp="👩🏻⚕">doctor | healthcare | light skin tone | nurse | therapist | woman | woman health worker</annotation> + <annotation cp="👩🏻⚕" type="tts">woman health worker: light skin tone</annotation> + <annotation cp="👩🏼⚕">doctor | healthcare | medium-light skin tone | nurse | therapist | woman | woman health worker</annotation> + <annotation cp="👩🏼⚕" type="tts">woman health worker: medium-light skin tone</annotation> + <annotation cp="👩🏽⚕">doctor | healthcare | medium skin tone | nurse | therapist | woman | woman health worker</annotation> + <annotation cp="👩🏽⚕" type="tts">woman health worker: medium skin tone</annotation> + <annotation cp="👩🏾⚕">doctor | healthcare | medium-dark skin tone | nurse | therapist | woman | woman health worker</annotation> + <annotation cp="👩🏾⚕" type="tts">woman health worker: medium-dark skin tone</annotation> + <annotation cp="👩🏿⚕">dark skin tone | doctor | healthcare | nurse | therapist | woman | woman health worker</annotation> + <annotation cp="👩🏿⚕" type="tts">woman health worker: dark skin tone</annotation> + <annotation cp="🧑🏻🎓">graduate | light skin tone | student</annotation> + <annotation cp="🧑🏻🎓" type="tts">student: light skin tone</annotation> + <annotation cp="🧑🏼🎓">graduate | medium-light skin tone | student</annotation> + <annotation cp="🧑🏼🎓" type="tts">student: medium-light skin tone</annotation> + <annotation cp="🧑🏽🎓">graduate | medium skin tone | student</annotation> + <annotation cp="🧑🏽🎓" type="tts">student: medium skin tone</annotation> + <annotation cp="🧑🏾🎓">graduate | medium-dark skin tone | student</annotation> + <annotation cp="🧑🏾🎓" type="tts">student: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🎓">dark skin tone | graduate | student</annotation> + <annotation cp="🧑🏿🎓" type="tts">student: dark skin tone</annotation> + <annotation cp="👨🏻🎓">graduate | light skin tone | man | student</annotation> + <annotation cp="👨🏻🎓" type="tts">man student: light skin tone</annotation> + <annotation cp="👨🏼🎓">graduate | man | medium-light skin tone | student</annotation> + <annotation cp="👨🏼🎓" type="tts">man student: medium-light skin tone</annotation> + <annotation cp="👨🏽🎓">graduate | man | medium skin tone | student</annotation> + <annotation cp="👨🏽🎓" type="tts">man student: medium skin tone</annotation> + <annotation cp="👨🏾🎓">graduate | man | medium-dark skin tone | student</annotation> + <annotation cp="👨🏾🎓" type="tts">man student: medium-dark skin tone</annotation> + <annotation cp="👨🏿🎓">dark skin tone | graduate | man | student</annotation> + <annotation cp="👨🏿🎓" type="tts">man student: dark skin tone</annotation> + <annotation cp="👩🏻🎓">graduate | light skin tone | student | woman</annotation> + <annotation cp="👩🏻🎓" type="tts">woman student: light skin tone</annotation> + <annotation cp="👩🏼🎓">graduate | medium-light skin tone | student | woman</annotation> + <annotation cp="👩🏼🎓" type="tts">woman student: medium-light skin tone</annotation> + <annotation cp="👩🏽🎓">graduate | medium skin tone | student | woman</annotation> + <annotation cp="👩🏽🎓" type="tts">woman student: medium skin tone</annotation> + <annotation cp="👩🏾🎓">graduate | medium-dark skin tone | student | woman</annotation> + <annotation cp="👩🏾🎓" type="tts">woman student: medium-dark skin tone</annotation> + <annotation cp="👩🏿🎓">dark skin tone | graduate | student | woman</annotation> + <annotation cp="👩🏿🎓" type="tts">woman student: dark skin tone</annotation> + <annotation cp="🧑🏻🏫">instructor | light skin tone | professor | teacher</annotation> + <annotation cp="🧑🏻🏫" type="tts">teacher: light skin tone</annotation> + <annotation cp="🧑🏼🏫">instructor | medium-light skin tone | professor | teacher</annotation> + <annotation cp="🧑🏼🏫" type="tts">teacher: medium-light skin tone</annotation> + <annotation cp="🧑🏽🏫">instructor | medium skin tone | professor | teacher</annotation> + <annotation cp="🧑🏽🏫" type="tts">teacher: medium skin tone</annotation> + <annotation cp="🧑🏾🏫">instructor | medium-dark skin tone | professor | teacher</annotation> + <annotation cp="🧑🏾🏫" type="tts">teacher: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🏫">dark skin tone | instructor | professor | teacher</annotation> + <annotation cp="🧑🏿🏫" type="tts">teacher: dark skin tone</annotation> + <annotation cp="👨🏻🏫">instructor | light skin tone | man | professor | teacher</annotation> + <annotation cp="👨🏻🏫" type="tts">man teacher: light skin tone</annotation> + <annotation cp="👨🏼🏫">instructor | man | medium-light skin tone | professor | teacher</annotation> + <annotation cp="👨🏼🏫" type="tts">man teacher: medium-light skin tone</annotation> + <annotation cp="👨🏽🏫">instructor | man | medium skin tone | professor | teacher</annotation> + <annotation cp="👨🏽🏫" type="tts">man teacher: medium skin tone</annotation> + <annotation cp="👨🏾🏫">instructor | man | medium-dark skin tone | professor | teacher</annotation> + <annotation cp="👨🏾🏫" type="tts">man teacher: medium-dark skin tone</annotation> + <annotation cp="👨🏿🏫">dark skin tone | instructor | man | professor | teacher</annotation> + <annotation cp="👨🏿🏫" type="tts">man teacher: dark skin tone</annotation> + <annotation cp="👩🏻🏫">instructor | light skin tone | professor | teacher | woman</annotation> + <annotation cp="👩🏻🏫" type="tts">woman teacher: light skin tone</annotation> + <annotation cp="👩🏼🏫">instructor | medium-light skin tone | professor | teacher | woman</annotation> + <annotation cp="👩🏼🏫" type="tts">woman teacher: medium-light skin tone</annotation> + <annotation cp="👩🏽🏫">instructor | medium skin tone | professor | teacher | woman</annotation> + <annotation cp="👩🏽🏫" type="tts">woman teacher: medium skin tone</annotation> + <annotation cp="👩🏾🏫">instructor | medium-dark skin tone | professor | teacher | woman</annotation> + <annotation cp="👩🏾🏫" type="tts">woman teacher: medium-dark skin tone</annotation> + <annotation cp="👩🏿🏫">dark skin tone | instructor | professor | teacher | woman</annotation> + <annotation cp="👩🏿🏫" type="tts">woman teacher: dark skin tone</annotation> + <annotation cp="🧑🏻⚖">judge | justice | light skin tone | scales</annotation> + <annotation cp="🧑🏻⚖" type="tts">judge: light skin tone</annotation> + <annotation cp="🧑🏼⚖">judge | justice | medium-light skin tone | scales</annotation> + <annotation cp="🧑🏼⚖" type="tts">judge: medium-light skin tone</annotation> + <annotation cp="🧑🏽⚖">judge | justice | medium skin tone | scales</annotation> + <annotation cp="🧑🏽⚖" type="tts">judge: medium skin tone</annotation> + <annotation cp="🧑🏾⚖">judge | justice | medium-dark skin tone | scales</annotation> + <annotation cp="🧑🏾⚖" type="tts">judge: medium-dark skin tone</annotation> + <annotation cp="🧑🏿⚖">dark skin tone | judge | justice | scales</annotation> + <annotation cp="🧑🏿⚖" type="tts">judge: dark skin tone</annotation> + <annotation cp="👨🏻⚖">judge | justice | light skin tone | man | scales</annotation> + <annotation cp="👨🏻⚖" type="tts">man judge: light skin tone</annotation> + <annotation cp="👨🏼⚖">judge | justice | man | medium-light skin tone | scales</annotation> + <annotation cp="👨🏼⚖" type="tts">man judge: medium-light skin tone</annotation> + <annotation cp="👨🏽⚖">judge | justice | man | medium skin tone | scales</annotation> + <annotation cp="👨🏽⚖" type="tts">man judge: medium skin tone</annotation> + <annotation cp="👨🏾⚖">judge | justice | man | medium-dark skin tone | scales</annotation> + <annotation cp="👨🏾⚖" type="tts">man judge: medium-dark skin tone</annotation> + <annotation cp="👨🏿⚖">dark skin tone | judge | justice | man | scales</annotation> + <annotation cp="👨🏿⚖" type="tts">man judge: dark skin tone</annotation> + <annotation cp="👩🏻⚖">judge | justice | light skin tone | scales | woman</annotation> + <annotation cp="👩🏻⚖" type="tts">woman judge: light skin tone</annotation> + <annotation cp="👩🏼⚖">judge | justice | medium-light skin tone | scales | woman</annotation> + <annotation cp="👩🏼⚖" type="tts">woman judge: medium-light skin tone</annotation> + <annotation cp="👩🏽⚖">judge | justice | medium skin tone | scales | woman</annotation> + <annotation cp="👩🏽⚖" type="tts">woman judge: medium skin tone</annotation> + <annotation cp="👩🏾⚖">judge | justice | medium-dark skin tone | scales | woman</annotation> + <annotation cp="👩🏾⚖" type="tts">woman judge: medium-dark skin tone</annotation> + <annotation cp="👩🏿⚖">dark skin tone | judge | justice | scales | woman</annotation> + <annotation cp="👩🏿⚖" type="tts">woman judge: dark skin tone</annotation> + <annotation cp="🧑🏻🌾">farmer | gardener | light skin tone | rancher</annotation> + <annotation cp="🧑🏻🌾" type="tts">farmer: light skin tone</annotation> + <annotation cp="🧑🏼🌾">farmer | gardener | medium-light skin tone | rancher</annotation> + <annotation cp="🧑🏼🌾" type="tts">farmer: medium-light skin tone</annotation> + <annotation cp="🧑🏽🌾">farmer | gardener | medium skin tone | rancher</annotation> + <annotation cp="🧑🏽🌾" type="tts">farmer: medium skin tone</annotation> + <annotation cp="🧑🏾🌾">farmer | gardener | medium-dark skin tone | rancher</annotation> + <annotation cp="🧑🏾🌾" type="tts">farmer: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🌾">dark skin tone | farmer | gardener | rancher</annotation> + <annotation cp="🧑🏿🌾" type="tts">farmer: dark skin tone</annotation> + <annotation cp="👨🏻🌾">farmer | gardener | light skin tone | man | rancher</annotation> + <annotation cp="👨🏻🌾" type="tts">man farmer: light skin tone</annotation> + <annotation cp="👨🏼🌾">farmer | gardener | man | medium-light skin tone | rancher</annotation> + <annotation cp="👨🏼🌾" type="tts">man farmer: medium-light skin tone</annotation> + <annotation cp="👨🏽🌾">farmer | gardener | man | medium skin tone | rancher</annotation> + <annotation cp="👨🏽🌾" type="tts">man farmer: medium skin tone</annotation> + <annotation cp="👨🏾🌾">farmer | gardener | man | medium-dark skin tone | rancher</annotation> + <annotation cp="👨🏾🌾" type="tts">man farmer: medium-dark skin tone</annotation> + <annotation cp="👨🏿🌾">dark skin tone | farmer | gardener | man | rancher</annotation> + <annotation cp="👨🏿🌾" type="tts">man farmer: dark skin tone</annotation> + <annotation cp="👩🏻🌾">farmer | gardener | light skin tone | rancher | woman</annotation> + <annotation cp="👩🏻🌾" type="tts">woman farmer: light skin tone</annotation> + <annotation cp="👩🏼🌾">farmer | gardener | medium-light skin tone | rancher | woman</annotation> + <annotation cp="👩🏼🌾" type="tts">woman farmer: medium-light skin tone</annotation> + <annotation cp="👩🏽🌾">farmer | gardener | medium skin tone | rancher | woman</annotation> + <annotation cp="👩🏽🌾" type="tts">woman farmer: medium skin tone</annotation> + <annotation cp="👩🏾🌾">farmer | gardener | medium-dark skin tone | rancher | woman</annotation> + <annotation cp="👩🏾🌾" type="tts">woman farmer: medium-dark skin tone</annotation> + <annotation cp="👩🏿🌾">dark skin tone | farmer | gardener | rancher | woman</annotation> + <annotation cp="👩🏿🌾" type="tts">woman farmer: dark skin tone</annotation> + <annotation cp="🧑🏻🍳">chef | cook | light skin tone</annotation> + <annotation cp="🧑🏻🍳" type="tts">cook: light skin tone</annotation> + <annotation cp="🧑🏼🍳">chef | cook | medium-light skin tone</annotation> + <annotation cp="🧑🏼🍳" type="tts">cook: medium-light skin tone</annotation> + <annotation cp="🧑🏽🍳">chef | cook | medium skin tone</annotation> + <annotation cp="🧑🏽🍳" type="tts">cook: medium skin tone</annotation> + <annotation cp="🧑🏾🍳">chef | cook | medium-dark skin tone</annotation> + <annotation cp="🧑🏾🍳" type="tts">cook: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🍳">chef | cook | dark skin tone</annotation> + <annotation cp="🧑🏿🍳" type="tts">cook: dark skin tone</annotation> + <annotation cp="👨🏻🍳">chef | cook | light skin tone | man</annotation> + <annotation cp="👨🏻🍳" type="tts">man cook: light skin tone</annotation> + <annotation cp="👨🏼🍳">chef | cook | man | medium-light skin tone</annotation> + <annotation cp="👨🏼🍳" type="tts">man cook: medium-light skin tone</annotation> + <annotation cp="👨🏽🍳">chef | cook | man | medium skin tone</annotation> + <annotation cp="👨🏽🍳" type="tts">man cook: medium skin tone</annotation> + <annotation cp="👨🏾🍳">chef | cook | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾🍳" type="tts">man cook: medium-dark skin tone</annotation> + <annotation cp="👨🏿🍳">chef | cook | dark skin tone | man</annotation> + <annotation cp="👨🏿🍳" type="tts">man cook: dark skin tone</annotation> + <annotation cp="👩🏻🍳">chef | cook | light skin tone | woman</annotation> + <annotation cp="👩🏻🍳" type="tts">woman cook: light skin tone</annotation> + <annotation cp="👩🏼🍳">chef | cook | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼🍳" type="tts">woman cook: medium-light skin tone</annotation> + <annotation cp="👩🏽🍳">chef | cook | medium skin tone | woman</annotation> + <annotation cp="👩🏽🍳" type="tts">woman cook: medium skin tone</annotation> + <annotation cp="👩🏾🍳">chef | cook | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾🍳" type="tts">woman cook: medium-dark skin tone</annotation> + <annotation cp="👩🏿🍳">chef | cook | dark skin tone | woman</annotation> + <annotation cp="👩🏿🍳" type="tts">woman cook: dark skin tone</annotation> + <annotation cp="🧑🏻🔧">electrician | light skin tone | mechanic | plumber | tradesperson</annotation> + <annotation cp="🧑🏻🔧" type="tts">mechanic: light skin tone</annotation> + <annotation cp="🧑🏼🔧">electrician | mechanic | medium-light skin tone | plumber | tradesperson</annotation> + <annotation cp="🧑🏼🔧" type="tts">mechanic: medium-light skin tone</annotation> + <annotation cp="🧑🏽🔧">electrician | mechanic | medium skin tone | plumber | tradesperson</annotation> + <annotation cp="🧑🏽🔧" type="tts">mechanic: medium skin tone</annotation> + <annotation cp="🧑🏾🔧">electrician | mechanic | medium-dark skin tone | plumber | tradesperson</annotation> + <annotation cp="🧑🏾🔧" type="tts">mechanic: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🔧">dark skin tone | electrician | mechanic | plumber | tradesperson</annotation> + <annotation cp="🧑🏿🔧" type="tts">mechanic: dark skin tone</annotation> + <annotation cp="👨🏻🔧">electrician | light skin tone | man | mechanic | plumber | tradesperson</annotation> + <annotation cp="👨🏻🔧" type="tts">man mechanic: light skin tone</annotation> + <annotation cp="👨🏼🔧">electrician | man | mechanic | medium-light skin tone | plumber | tradesperson</annotation> + <annotation cp="👨🏼🔧" type="tts">man mechanic: medium-light skin tone</annotation> + <annotation cp="👨🏽🔧">electrician | man | mechanic | medium skin tone | plumber | tradesperson</annotation> + <annotation cp="👨🏽🔧" type="tts">man mechanic: medium skin tone</annotation> + <annotation cp="👨🏾🔧">electrician | man | mechanic | medium-dark skin tone | plumber | tradesperson</annotation> + <annotation cp="👨🏾🔧" type="tts">man mechanic: medium-dark skin tone</annotation> + <annotation cp="👨🏿🔧">dark skin tone | electrician | man | mechanic | plumber | tradesperson</annotation> + <annotation cp="👨🏿🔧" type="tts">man mechanic: dark skin tone</annotation> + <annotation cp="👩🏻🔧">electrician | light skin tone | mechanic | plumber | tradesperson | woman</annotation> + <annotation cp="👩🏻🔧" type="tts">woman mechanic: light skin tone</annotation> + <annotation cp="👩🏼🔧">electrician | mechanic | medium-light skin tone | plumber | tradesperson | woman</annotation> + <annotation cp="👩🏼🔧" type="tts">woman mechanic: medium-light skin tone</annotation> + <annotation cp="👩🏽🔧">electrician | mechanic | medium skin tone | plumber | tradesperson | woman</annotation> + <annotation cp="👩🏽🔧" type="tts">woman mechanic: medium skin tone</annotation> + <annotation cp="👩🏾🔧">electrician | mechanic | medium-dark skin tone | plumber | tradesperson | woman</annotation> + <annotation cp="👩🏾🔧" type="tts">woman mechanic: medium-dark skin tone</annotation> + <annotation cp="👩🏿🔧">dark skin tone | electrician | mechanic | plumber | tradesperson | woman</annotation> + <annotation cp="👩🏿🔧" type="tts">woman mechanic: dark skin tone</annotation> + <annotation cp="🧑🏻🏭">assembly | factory | industrial | light skin tone | worker</annotation> + <annotation cp="🧑🏻🏭" type="tts">factory worker: light skin tone</annotation> + <annotation cp="🧑🏼🏭">assembly | factory | industrial | medium-light skin tone | worker</annotation> + <annotation cp="🧑🏼🏭" type="tts">factory worker: medium-light skin tone</annotation> + <annotation cp="🧑🏽🏭">assembly | factory | industrial | medium skin tone | worker</annotation> + <annotation cp="🧑🏽🏭" type="tts">factory worker: medium skin tone</annotation> + <annotation cp="🧑🏾🏭">assembly | factory | industrial | medium-dark skin tone | worker</annotation> + <annotation cp="🧑🏾🏭" type="tts">factory worker: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🏭">assembly | dark skin tone | factory | industrial | worker</annotation> + <annotation cp="🧑🏿🏭" type="tts">factory worker: dark skin tone</annotation> + <annotation cp="👨🏻🏭">assembly | factory | industrial | light skin tone | man | worker</annotation> + <annotation cp="👨🏻🏭" type="tts">man factory worker: light skin tone</annotation> + <annotation cp="👨🏼🏭">assembly | factory | industrial | man | medium-light skin tone | worker</annotation> + <annotation cp="👨🏼🏭" type="tts">man factory worker: medium-light skin tone</annotation> + <annotation cp="👨🏽🏭">assembly | factory | industrial | man | medium skin tone | worker</annotation> + <annotation cp="👨🏽🏭" type="tts">man factory worker: medium skin tone</annotation> + <annotation cp="👨🏾🏭">assembly | factory | industrial | man | medium-dark skin tone | worker</annotation> + <annotation cp="👨🏾🏭" type="tts">man factory worker: medium-dark skin tone</annotation> + <annotation cp="👨🏿🏭">assembly | dark skin tone | factory | industrial | man | worker</annotation> + <annotation cp="👨🏿🏭" type="tts">man factory worker: dark skin tone</annotation> + <annotation cp="👩🏻🏭">assembly | factory | industrial | light skin tone | woman | worker</annotation> + <annotation cp="👩🏻🏭" type="tts">woman factory worker: light skin tone</annotation> + <annotation cp="👩🏼🏭">assembly | factory | industrial | medium-light skin tone | woman | worker</annotation> + <annotation cp="👩🏼🏭" type="tts">woman factory worker: medium-light skin tone</annotation> + <annotation cp="👩🏽🏭">assembly | factory | industrial | medium skin tone | woman | worker</annotation> + <annotation cp="👩🏽🏭" type="tts">woman factory worker: medium skin tone</annotation> + <annotation cp="👩🏾🏭">assembly | factory | industrial | medium-dark skin tone | woman | worker</annotation> + <annotation cp="👩🏾🏭" type="tts">woman factory worker: medium-dark skin tone</annotation> + <annotation cp="👩🏿🏭">assembly | dark skin tone | factory | industrial | woman | worker</annotation> + <annotation cp="👩🏿🏭" type="tts">woman factory worker: dark skin tone</annotation> + <annotation cp="🧑🏻💼">architect | business | light skin tone | manager | office worker | white-collar</annotation> + <annotation cp="🧑🏻💼" type="tts">office worker: light skin tone</annotation> + <annotation cp="🧑🏼💼">architect | business | manager | medium-light skin tone | office worker | white-collar</annotation> + <annotation cp="🧑🏼💼" type="tts">office worker: medium-light skin tone</annotation> + <annotation cp="🧑🏽💼">architect | business | manager | medium skin tone | office worker | white-collar</annotation> + <annotation cp="🧑🏽💼" type="tts">office worker: medium skin tone</annotation> + <annotation cp="🧑🏾💼">architect | business | manager | medium-dark skin tone | office worker | white-collar</annotation> + <annotation cp="🧑🏾💼" type="tts">office worker: medium-dark skin tone</annotation> + <annotation cp="🧑🏿💼">architect | business | dark skin tone | manager | office worker | white-collar</annotation> + <annotation cp="🧑🏿💼" type="tts">office worker: dark skin tone</annotation> + <annotation cp="👨🏻💼">architect | business | light skin tone | man | man office worker | manager | white-collar</annotation> + <annotation cp="👨🏻💼" type="tts">man office worker: light skin tone</annotation> + <annotation cp="👨🏼💼">architect | business | man | man office worker | manager | medium-light skin tone | white-collar</annotation> + <annotation cp="👨🏼💼" type="tts">man office worker: medium-light skin tone</annotation> + <annotation cp="👨🏽💼">architect | business | man | man office worker | manager | medium skin tone | white-collar</annotation> + <annotation cp="👨🏽💼" type="tts">man office worker: medium skin tone</annotation> + <annotation cp="👨🏾💼">architect | business | man | man office worker | manager | medium-dark skin tone | white-collar</annotation> + <annotation cp="👨🏾💼" type="tts">man office worker: medium-dark skin tone</annotation> + <annotation cp="👨🏿💼">architect | business | dark skin tone | man | man office worker | manager | white-collar</annotation> + <annotation cp="👨🏿💼" type="tts">man office worker: dark skin tone</annotation> + <annotation cp="👩🏻💼">architect | business | light skin tone | manager | white-collar | woman | woman office worker</annotation> + <annotation cp="👩🏻💼" type="tts">woman office worker: light skin tone</annotation> + <annotation cp="👩🏼💼">architect | business | manager | medium-light skin tone | white-collar | woman | woman office worker</annotation> + <annotation cp="👩🏼💼" type="tts">woman office worker: medium-light skin tone</annotation> + <annotation cp="👩🏽💼">architect | business | manager | medium skin tone | white-collar | woman | woman office worker</annotation> + <annotation cp="👩🏽💼" type="tts">woman office worker: medium skin tone</annotation> + <annotation cp="👩🏾💼">architect | business | manager | medium-dark skin tone | white-collar | woman | woman office worker</annotation> + <annotation cp="👩🏾💼" type="tts">woman office worker: medium-dark skin tone</annotation> + <annotation cp="👩🏿💼">architect | business | dark skin tone | manager | white-collar | woman | woman office worker</annotation> + <annotation cp="👩🏿💼" type="tts">woman office worker: dark skin tone</annotation> + <annotation cp="🧑🏻🔬">biologist | chemist | engineer | light skin tone | physicist | scientist</annotation> + <annotation cp="🧑🏻🔬" type="tts">scientist: light skin tone</annotation> + <annotation cp="🧑🏼🔬">biologist | chemist | engineer | medium-light skin tone | physicist | scientist</annotation> + <annotation cp="🧑🏼🔬" type="tts">scientist: medium-light skin tone</annotation> + <annotation cp="🧑🏽🔬">biologist | chemist | engineer | medium skin tone | physicist | scientist</annotation> + <annotation cp="🧑🏽🔬" type="tts">scientist: medium skin tone</annotation> + <annotation cp="🧑🏾🔬">biologist | chemist | engineer | medium-dark skin tone | physicist | scientist</annotation> + <annotation cp="🧑🏾🔬" type="tts">scientist: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🔬">biologist | chemist | dark skin tone | engineer | physicist | scientist</annotation> + <annotation cp="🧑🏿🔬" type="tts">scientist: dark skin tone</annotation> + <annotation cp="👨🏻🔬">biologist | chemist | engineer | light skin tone | man | physicist | scientist</annotation> + <annotation cp="👨🏻🔬" type="tts">man scientist: light skin tone</annotation> + <annotation cp="👨🏼🔬">biologist | chemist | engineer | man | medium-light skin tone | physicist | scientist</annotation> + <annotation cp="👨🏼🔬" type="tts">man scientist: medium-light skin tone</annotation> + <annotation cp="👨🏽🔬">biologist | chemist | engineer | man | medium skin tone | physicist | scientist</annotation> + <annotation cp="👨🏽🔬" type="tts">man scientist: medium skin tone</annotation> + <annotation cp="👨🏾🔬">biologist | chemist | engineer | man | medium-dark skin tone | physicist | scientist</annotation> + <annotation cp="👨🏾🔬" type="tts">man scientist: medium-dark skin tone</annotation> + <annotation cp="👨🏿🔬">biologist | chemist | dark skin tone | engineer | man | physicist | scientist</annotation> + <annotation cp="👨🏿🔬" type="tts">man scientist: dark skin tone</annotation> + <annotation cp="👩🏻🔬">biologist | chemist | engineer | light skin tone | physicist | scientist | woman</annotation> + <annotation cp="👩🏻🔬" type="tts">woman scientist: light skin tone</annotation> + <annotation cp="👩🏼🔬">biologist | chemist | engineer | medium-light skin tone | physicist | scientist | woman</annotation> + <annotation cp="👩🏼🔬" type="tts">woman scientist: medium-light skin tone</annotation> + <annotation cp="👩🏽🔬">biologist | chemist | engineer | medium skin tone | physicist | scientist | woman</annotation> + <annotation cp="👩🏽🔬" type="tts">woman scientist: medium skin tone</annotation> + <annotation cp="👩🏾🔬">biologist | chemist | engineer | medium-dark skin tone | physicist | scientist | woman</annotation> + <annotation cp="👩🏾🔬" type="tts">woman scientist: medium-dark skin tone</annotation> + <annotation cp="👩🏿🔬">biologist | chemist | dark skin tone | engineer | physicist | scientist | woman</annotation> + <annotation cp="👩🏿🔬" type="tts">woman scientist: dark skin tone</annotation> + <annotation cp="🧑🏻💻">coder | developer | inventor | light skin tone | software | technologist</annotation> + <annotation cp="🧑🏻💻" type="tts">technologist: light skin tone</annotation> + <annotation cp="🧑🏼💻">coder | developer | inventor | medium-light skin tone | software | technologist</annotation> + <annotation cp="🧑🏼💻" type="tts">technologist: medium-light skin tone</annotation> + <annotation cp="🧑🏽💻">coder | developer | inventor | medium skin tone | software | technologist</annotation> + <annotation cp="🧑🏽💻" type="tts">technologist: medium skin tone</annotation> + <annotation cp="🧑🏾💻">coder | developer | inventor | medium-dark skin tone | software | technologist</annotation> + <annotation cp="🧑🏾💻" type="tts">technologist: medium-dark skin tone</annotation> + <annotation cp="🧑🏿💻">coder | dark skin tone | developer | inventor | software | technologist</annotation> + <annotation cp="🧑🏿💻" type="tts">technologist: dark skin tone</annotation> + <annotation cp="👨🏻💻">coder | developer | inventor | light skin tone | man | software | technologist</annotation> + <annotation cp="👨🏻💻" type="tts">man technologist: light skin tone</annotation> + <annotation cp="👨🏼💻">coder | developer | inventor | man | medium-light skin tone | software | technologist</annotation> + <annotation cp="👨🏼💻" type="tts">man technologist: medium-light skin tone</annotation> + <annotation cp="👨🏽💻">coder | developer | inventor | man | medium skin tone | software | technologist</annotation> + <annotation cp="👨🏽💻" type="tts">man technologist: medium skin tone</annotation> + <annotation cp="👨🏾💻">coder | developer | inventor | man | medium-dark skin tone | software | technologist</annotation> + <annotation cp="👨🏾💻" type="tts">man technologist: medium-dark skin tone</annotation> + <annotation cp="👨🏿💻">coder | dark skin tone | developer | inventor | man | software | technologist</annotation> + <annotation cp="👨🏿💻" type="tts">man technologist: dark skin tone</annotation> + <annotation cp="👩🏻💻">coder | developer | inventor | light skin tone | software | technologist | woman</annotation> + <annotation cp="👩🏻💻" type="tts">woman technologist: light skin tone</annotation> + <annotation cp="👩🏼💻">coder | developer | inventor | medium-light skin tone | software | technologist | woman</annotation> + <annotation cp="👩🏼💻" type="tts">woman technologist: medium-light skin tone</annotation> + <annotation cp="👩🏽💻">coder | developer | inventor | medium skin tone | software | technologist | woman</annotation> + <annotation cp="👩🏽💻" type="tts">woman technologist: medium skin tone</annotation> + <annotation cp="👩🏾💻">coder | developer | inventor | medium-dark skin tone | software | technologist | woman</annotation> + <annotation cp="👩🏾💻" type="tts">woman technologist: medium-dark skin tone</annotation> + <annotation cp="👩🏿💻">coder | dark skin tone | developer | inventor | software | technologist | woman</annotation> + <annotation cp="👩🏿💻" type="tts">woman technologist: dark skin tone</annotation> + <annotation cp="🧑🏻🎤">actor | entertainer | light skin tone | rock | singer | star</annotation> + <annotation cp="🧑🏻🎤" type="tts">singer: light skin tone</annotation> + <annotation cp="🧑🏼🎤">actor | entertainer | medium-light skin tone | rock | singer | star</annotation> + <annotation cp="🧑🏼🎤" type="tts">singer: medium-light skin tone</annotation> + <annotation cp="🧑🏽🎤">actor | entertainer | medium skin tone | rock | singer | star</annotation> + <annotation cp="🧑🏽🎤" type="tts">singer: medium skin tone</annotation> + <annotation cp="🧑🏾🎤">actor | entertainer | medium-dark skin tone | rock | singer | star</annotation> + <annotation cp="🧑🏾🎤" type="tts">singer: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🎤">actor | dark skin tone | entertainer | rock | singer | star</annotation> + <annotation cp="🧑🏿🎤" type="tts">singer: dark skin tone</annotation> + <annotation cp="👨🏻🎤">actor | entertainer | light skin tone | man | rock | singer | star</annotation> + <annotation cp="👨🏻🎤" type="tts">man singer: light skin tone</annotation> + <annotation cp="👨🏼🎤">actor | entertainer | man | medium-light skin tone | rock | singer | star</annotation> + <annotation cp="👨🏼🎤" type="tts">man singer: medium-light skin tone</annotation> + <annotation cp="👨🏽🎤">actor | entertainer | man | medium skin tone | rock | singer | star</annotation> + <annotation cp="👨🏽🎤" type="tts">man singer: medium skin tone</annotation> + <annotation cp="👨🏾🎤">actor | entertainer | man | medium-dark skin tone | rock | singer | star</annotation> + <annotation cp="👨🏾🎤" type="tts">man singer: medium-dark skin tone</annotation> + <annotation cp="👨🏿🎤">actor | dark skin tone | entertainer | man | rock | singer | star</annotation> + <annotation cp="👨🏿🎤" type="tts">man singer: dark skin tone</annotation> + <annotation cp="👩🏻🎤">actor | entertainer | light skin tone | rock | singer | star | woman</annotation> + <annotation cp="👩🏻🎤" type="tts">woman singer: light skin tone</annotation> + <annotation cp="👩🏼🎤">actor | entertainer | medium-light skin tone | rock | singer | star | woman</annotation> + <annotation cp="👩🏼🎤" type="tts">woman singer: medium-light skin tone</annotation> + <annotation cp="👩🏽🎤">actor | entertainer | medium skin tone | rock | singer | star | woman</annotation> + <annotation cp="👩🏽🎤" type="tts">woman singer: medium skin tone</annotation> + <annotation cp="👩🏾🎤">actor | entertainer | medium-dark skin tone | rock | singer | star | woman</annotation> + <annotation cp="👩🏾🎤" type="tts">woman singer: medium-dark skin tone</annotation> + <annotation cp="👩🏿🎤">actor | dark skin tone | entertainer | rock | singer | star | woman</annotation> + <annotation cp="👩🏿🎤" type="tts">woman singer: dark skin tone</annotation> + <annotation cp="🧑🏻🎨">artist | light skin tone | palette</annotation> + <annotation cp="🧑🏻🎨" type="tts">artist: light skin tone</annotation> + <annotation cp="🧑🏼🎨">artist | medium-light skin tone | palette</annotation> + <annotation cp="🧑🏼🎨" type="tts">artist: medium-light skin tone</annotation> + <annotation cp="🧑🏽🎨">artist | medium skin tone | palette</annotation> + <annotation cp="🧑🏽🎨" type="tts">artist: medium skin tone</annotation> + <annotation cp="🧑🏾🎨">artist | medium-dark skin tone | palette</annotation> + <annotation cp="🧑🏾🎨" type="tts">artist: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🎨">artist | dark skin tone | palette</annotation> + <annotation cp="🧑🏿🎨" type="tts">artist: dark skin tone</annotation> + <annotation cp="👨🏻🎨">artist | light skin tone | man | palette</annotation> + <annotation cp="👨🏻🎨" type="tts">man artist: light skin tone</annotation> + <annotation cp="👨🏼🎨">artist | man | medium-light skin tone | palette</annotation> + <annotation cp="👨🏼🎨" type="tts">man artist: medium-light skin tone</annotation> + <annotation cp="👨🏽🎨">artist | man | medium skin tone | palette</annotation> + <annotation cp="👨🏽🎨" type="tts">man artist: medium skin tone</annotation> + <annotation cp="👨🏾🎨">artist | man | medium-dark skin tone | palette</annotation> + <annotation cp="👨🏾🎨" type="tts">man artist: medium-dark skin tone</annotation> + <annotation cp="👨🏿🎨">artist | dark skin tone | man | palette</annotation> + <annotation cp="👨🏿🎨" type="tts">man artist: dark skin tone</annotation> + <annotation cp="👩🏻🎨">artist | light skin tone | palette | woman</annotation> + <annotation cp="👩🏻🎨" type="tts">woman artist: light skin tone</annotation> + <annotation cp="👩🏼🎨">artist | medium-light skin tone | palette | woman</annotation> + <annotation cp="👩🏼🎨" type="tts">woman artist: medium-light skin tone</annotation> + <annotation cp="👩🏽🎨">artist | medium skin tone | palette | woman</annotation> + <annotation cp="👩🏽🎨" type="tts">woman artist: medium skin tone</annotation> + <annotation cp="👩🏾🎨">artist | medium-dark skin tone | palette | woman</annotation> + <annotation cp="👩🏾🎨" type="tts">woman artist: medium-dark skin tone</annotation> + <annotation cp="👩🏿🎨">artist | dark skin tone | palette | woman</annotation> + <annotation cp="👩🏿🎨" type="tts">woman artist: dark skin tone</annotation> + <annotation cp="🧑🏻✈">light skin tone | pilot | plane</annotation> + <annotation cp="🧑🏻✈" type="tts">pilot: light skin tone</annotation> + <annotation cp="🧑🏼✈">medium-light skin tone | pilot | plane</annotation> + <annotation cp="🧑🏼✈" type="tts">pilot: medium-light skin tone</annotation> + <annotation cp="🧑🏽✈">medium skin tone | pilot | plane</annotation> + <annotation cp="🧑🏽✈" type="tts">pilot: medium skin tone</annotation> + <annotation cp="🧑🏾✈">medium-dark skin tone | pilot | plane</annotation> + <annotation cp="🧑🏾✈" type="tts">pilot: medium-dark skin tone</annotation> + <annotation cp="🧑🏿✈">dark skin tone | pilot | plane</annotation> + <annotation cp="🧑🏿✈" type="tts">pilot: dark skin tone</annotation> + <annotation cp="👨🏻✈">light skin tone | man | pilot | plane</annotation> + <annotation cp="👨🏻✈" type="tts">man pilot: light skin tone</annotation> + <annotation cp="👨🏼✈">man | medium-light skin tone | pilot | plane</annotation> + <annotation cp="👨🏼✈" type="tts">man pilot: medium-light skin tone</annotation> + <annotation cp="👨🏽✈">man | medium skin tone | pilot | plane</annotation> + <annotation cp="👨🏽✈" type="tts">man pilot: medium skin tone</annotation> + <annotation cp="👨🏾✈">man | medium-dark skin tone | pilot | plane</annotation> + <annotation cp="👨🏾✈" type="tts">man pilot: medium-dark skin tone</annotation> + <annotation cp="👨🏿✈">dark skin tone | man | pilot | plane</annotation> + <annotation cp="👨🏿✈" type="tts">man pilot: dark skin tone</annotation> + <annotation cp="👩🏻✈">light skin tone | pilot | plane | woman</annotation> + <annotation cp="👩🏻✈" type="tts">woman pilot: light skin tone</annotation> + <annotation cp="👩🏼✈">medium-light skin tone | pilot | plane | woman</annotation> + <annotation cp="👩🏼✈" type="tts">woman pilot: medium-light skin tone</annotation> + <annotation cp="👩🏽✈">medium skin tone | pilot | plane | woman</annotation> + <annotation cp="👩🏽✈" type="tts">woman pilot: medium skin tone</annotation> + <annotation cp="👩🏾✈">medium-dark skin tone | pilot | plane | woman</annotation> + <annotation cp="👩🏾✈" type="tts">woman pilot: medium-dark skin tone</annotation> + <annotation cp="👩🏿✈">dark skin tone | pilot | plane | woman</annotation> + <annotation cp="👩🏿✈" type="tts">woman pilot: dark skin tone</annotation> + <annotation cp="🧑🏻🚀">astronaut | light skin tone | rocket</annotation> + <annotation cp="🧑🏻🚀" type="tts">astronaut: light skin tone</annotation> + <annotation cp="🧑🏼🚀">astronaut | medium-light skin tone | rocket</annotation> + <annotation cp="🧑🏼🚀" type="tts">astronaut: medium-light skin tone</annotation> + <annotation cp="🧑🏽🚀">astronaut | medium skin tone | rocket</annotation> + <annotation cp="🧑🏽🚀" type="tts">astronaut: medium skin tone</annotation> + <annotation cp="🧑🏾🚀">astronaut | medium-dark skin tone | rocket</annotation> + <annotation cp="🧑🏾🚀" type="tts">astronaut: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🚀">astronaut | dark skin tone | rocket</annotation> + <annotation cp="🧑🏿🚀" type="tts">astronaut: dark skin tone</annotation> + <annotation cp="👨🏻🚀">astronaut | light skin tone | man | rocket</annotation> + <annotation cp="👨🏻🚀" type="tts">man astronaut: light skin tone</annotation> + <annotation cp="👨🏼🚀">astronaut | man | medium-light skin tone | rocket</annotation> + <annotation cp="👨🏼🚀" type="tts">man astronaut: medium-light skin tone</annotation> + <annotation cp="👨🏽🚀">astronaut | man | medium skin tone | rocket</annotation> + <annotation cp="👨🏽🚀" type="tts">man astronaut: medium skin tone</annotation> + <annotation cp="👨🏾🚀">astronaut | man | medium-dark skin tone | rocket</annotation> + <annotation cp="👨🏾🚀" type="tts">man astronaut: medium-dark skin tone</annotation> + <annotation cp="👨🏿🚀">astronaut | dark skin tone | man | rocket</annotation> + <annotation cp="👨🏿🚀" type="tts">man astronaut: dark skin tone</annotation> + <annotation cp="👩🏻🚀">astronaut | light skin tone | rocket | woman</annotation> + <annotation cp="👩🏻🚀" type="tts">woman astronaut: light skin tone</annotation> + <annotation cp="👩🏼🚀">astronaut | medium-light skin tone | rocket | woman</annotation> + <annotation cp="👩🏼🚀" type="tts">woman astronaut: medium-light skin tone</annotation> + <annotation cp="👩🏽🚀">astronaut | medium skin tone | rocket | woman</annotation> + <annotation cp="👩🏽🚀" type="tts">woman astronaut: medium skin tone</annotation> + <annotation cp="👩🏾🚀">astronaut | medium-dark skin tone | rocket | woman</annotation> + <annotation cp="👩🏾🚀" type="tts">woman astronaut: medium-dark skin tone</annotation> + <annotation cp="👩🏿🚀">astronaut | dark skin tone | rocket | woman</annotation> + <annotation cp="👩🏿🚀" type="tts">woman astronaut: dark skin tone</annotation> + <annotation cp="🧑🏻🚒">firefighter | firetruck | light skin tone</annotation> + <annotation cp="🧑🏻🚒" type="tts">firefighter: light skin tone</annotation> + <annotation cp="🧑🏼🚒">firefighter | firetruck | medium-light skin tone</annotation> + <annotation cp="🧑🏼🚒" type="tts">firefighter: medium-light skin tone</annotation> + <annotation cp="🧑🏽🚒">firefighter | firetruck | medium skin tone</annotation> + <annotation cp="🧑🏽🚒" type="tts">firefighter: medium skin tone</annotation> + <annotation cp="🧑🏾🚒">firefighter | firetruck | medium-dark skin tone</annotation> + <annotation cp="🧑🏾🚒" type="tts">firefighter: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🚒">dark skin tone | firefighter | firetruck</annotation> + <annotation cp="🧑🏿🚒" type="tts">firefighter: dark skin tone</annotation> + <annotation cp="👨🏻🚒">firefighter | firetruck | light skin tone | man</annotation> + <annotation cp="👨🏻🚒" type="tts">man firefighter: light skin tone</annotation> + <annotation cp="👨🏼🚒">firefighter | firetruck | man | medium-light skin tone</annotation> + <annotation cp="👨🏼🚒" type="tts">man firefighter: medium-light skin tone</annotation> + <annotation cp="👨🏽🚒">firefighter | firetruck | man | medium skin tone</annotation> + <annotation cp="👨🏽🚒" type="tts">man firefighter: medium skin tone</annotation> + <annotation cp="👨🏾🚒">firefighter | firetruck | man | medium-dark skin tone</annotation> + <annotation cp="👨🏾🚒" type="tts">man firefighter: medium-dark skin tone</annotation> + <annotation cp="👨🏿🚒">dark skin tone | firefighter | firetruck | man</annotation> + <annotation cp="👨🏿🚒" type="tts">man firefighter: dark skin tone</annotation> + <annotation cp="👩🏻🚒">firefighter | firetruck | light skin tone | woman</annotation> + <annotation cp="👩🏻🚒" type="tts">woman firefighter: light skin tone</annotation> + <annotation cp="👩🏼🚒">firefighter | firetruck | medium-light skin tone | woman</annotation> + <annotation cp="👩🏼🚒" type="tts">woman firefighter: medium-light skin tone</annotation> + <annotation cp="👩🏽🚒">firefighter | firetruck | medium skin tone | woman</annotation> + <annotation cp="👩🏽🚒" type="tts">woman firefighter: medium skin tone</annotation> + <annotation cp="👩🏾🚒">firefighter | firetruck | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏾🚒" type="tts">woman firefighter: medium-dark skin tone</annotation> + <annotation cp="👩🏿🚒">dark skin tone | firefighter | firetruck | woman</annotation> + <annotation cp="👩🏿🚒" type="tts">woman firefighter: dark skin tone</annotation> + <annotation cp="👮🏻">cop | light skin tone | officer | police</annotation> + <annotation cp="👮🏻" type="tts">police officer: light skin tone</annotation> + <annotation cp="👮🏼">cop | medium-light skin tone | officer | police</annotation> + <annotation cp="👮🏼" type="tts">police officer: medium-light skin tone</annotation> + <annotation cp="👮🏽">cop | medium skin tone | officer | police</annotation> + <annotation cp="👮🏽" type="tts">police officer: medium skin tone</annotation> + <annotation cp="👮🏾">cop | medium-dark skin tone | officer | police</annotation> + <annotation cp="👮🏾" type="tts">police officer: medium-dark skin tone</annotation> + <annotation cp="👮🏿">cop | dark skin tone | officer | police</annotation> + <annotation cp="👮🏿" type="tts">police officer: dark skin tone</annotation> + <annotation cp="👮🏻♂">cop | light skin tone | man | officer | police</annotation> + <annotation cp="👮🏻♂" type="tts">man police officer: light skin tone</annotation> + <annotation cp="👮🏼♂">cop | man | medium-light skin tone | officer | police</annotation> + <annotation cp="👮🏼♂" type="tts">man police officer: medium-light skin tone</annotation> + <annotation cp="👮🏽♂">cop | man | medium skin tone | officer | police</annotation> + <annotation cp="👮🏽♂" type="tts">man police officer: medium skin tone</annotation> + <annotation cp="👮🏾♂">cop | man | medium-dark skin tone | officer | police</annotation> + <annotation cp="👮🏾♂" type="tts">man police officer: medium-dark skin tone</annotation> + <annotation cp="👮🏿♂">cop | dark skin tone | man | officer | police</annotation> + <annotation cp="👮🏿♂" type="tts">man police officer: dark skin tone</annotation> + <annotation cp="👮🏻♀">cop | light skin tone | officer | police | woman</annotation> + <annotation cp="👮🏻♀" type="tts">woman police officer: light skin tone</annotation> + <annotation cp="👮🏼♀">cop | medium-light skin tone | officer | police | woman</annotation> + <annotation cp="👮🏼♀" type="tts">woman police officer: medium-light skin tone</annotation> + <annotation cp="👮🏽♀">cop | medium skin tone | officer | police | woman</annotation> + <annotation cp="👮🏽♀" type="tts">woman police officer: medium skin tone</annotation> + <annotation cp="👮🏾♀">cop | medium-dark skin tone | officer | police | woman</annotation> + <annotation cp="👮🏾♀" type="tts">woman police officer: medium-dark skin tone</annotation> + <annotation cp="👮🏿♀">cop | dark skin tone | officer | police | woman</annotation> + <annotation cp="👮🏿♀" type="tts">woman police officer: dark skin tone</annotation> + <annotation cp="🕵🏻">detective | light skin tone | sleuth | spy</annotation> + <annotation cp="🕵🏻" type="tts">detective: light skin tone</annotation> + <annotation cp="🕵🏼">detective | medium-light skin tone | sleuth | spy</annotation> + <annotation cp="🕵🏼" type="tts">detective: medium-light skin tone</annotation> + <annotation cp="🕵🏽">detective | medium skin tone | sleuth | spy</annotation> + <annotation cp="🕵🏽" type="tts">detective: medium skin tone</annotation> + <annotation cp="🕵🏾">detective | medium-dark skin tone | sleuth | spy</annotation> + <annotation cp="🕵🏾" type="tts">detective: medium-dark skin tone</annotation> + <annotation cp="🕵🏿">dark skin tone | detective | sleuth | spy</annotation> + <annotation cp="🕵🏿" type="tts">detective: dark skin tone</annotation> + <annotation cp="🕵🏻♂">detective | light skin tone | man | sleuth | spy</annotation> + <annotation cp="🕵🏻♂" type="tts">man detective: light skin tone</annotation> + <annotation cp="🕵🏼♂">detective | man | medium-light skin tone | sleuth | spy</annotation> + <annotation cp="🕵🏼♂" type="tts">man detective: medium-light skin tone</annotation> + <annotation cp="🕵🏽♂">detective | man | medium skin tone | sleuth | spy</annotation> + <annotation cp="🕵🏽♂" type="tts">man detective: medium skin tone</annotation> + <annotation cp="🕵🏾♂">detective | man | medium-dark skin tone | sleuth | spy</annotation> + <annotation cp="🕵🏾♂" type="tts">man detective: medium-dark skin tone</annotation> + <annotation cp="🕵🏿♂">dark skin tone | detective | man | sleuth | spy</annotation> + <annotation cp="🕵🏿♂" type="tts">man detective: dark skin tone</annotation> + <annotation cp="🕵🏻♀">detective | light skin tone | sleuth | spy | woman</annotation> + <annotation cp="🕵🏻♀" type="tts">woman detective: light skin tone</annotation> + <annotation cp="🕵🏼♀">detective | medium-light skin tone | sleuth | spy | woman</annotation> + <annotation cp="🕵🏼♀" type="tts">woman detective: medium-light skin tone</annotation> + <annotation cp="🕵🏽♀">detective | medium skin tone | sleuth | spy | woman</annotation> + <annotation cp="🕵🏽♀" type="tts">woman detective: medium skin tone</annotation> + <annotation cp="🕵🏾♀">detective | medium-dark skin tone | sleuth | spy | woman</annotation> + <annotation cp="🕵🏾♀" type="tts">woman detective: medium-dark skin tone</annotation> + <annotation cp="🕵🏿♀">dark skin tone | detective | sleuth | spy | woman</annotation> + <annotation cp="🕵🏿♀" type="tts">woman detective: dark skin tone</annotation> + <annotation cp="💂🏻">guard | light skin tone</annotation> + <annotation cp="💂🏻" type="tts">guard: light skin tone</annotation> + <annotation cp="💂🏼">guard | medium-light skin tone</annotation> + <annotation cp="💂🏼" type="tts">guard: medium-light skin tone</annotation> + <annotation cp="💂🏽">guard | medium skin tone</annotation> + <annotation cp="💂🏽" type="tts">guard: medium skin tone</annotation> + <annotation cp="💂🏾">guard | medium-dark skin tone</annotation> + <annotation cp="💂🏾" type="tts">guard: medium-dark skin tone</annotation> + <annotation cp="💂🏿">dark skin tone | guard</annotation> + <annotation cp="💂🏿" type="tts">guard: dark skin tone</annotation> + <annotation cp="💂🏻♂">guard | light skin tone | man</annotation> + <annotation cp="💂🏻♂" type="tts">man guard: light skin tone</annotation> + <annotation cp="💂🏼♂">guard | man | medium-light skin tone</annotation> + <annotation cp="💂🏼♂" type="tts">man guard: medium-light skin tone</annotation> + <annotation cp="💂🏽♂">guard | man | medium skin tone</annotation> + <annotation cp="💂🏽♂" type="tts">man guard: medium skin tone</annotation> + <annotation cp="💂🏾♂">guard | man | medium-dark skin tone</annotation> + <annotation cp="💂🏾♂" type="tts">man guard: medium-dark skin tone</annotation> + <annotation cp="💂🏿♂">dark skin tone | guard | man</annotation> + <annotation cp="💂🏿♂" type="tts">man guard: dark skin tone</annotation> + <annotation cp="💂🏻♀">guard | light skin tone | woman</annotation> + <annotation cp="💂🏻♀" type="tts">woman guard: light skin tone</annotation> + <annotation cp="💂🏼♀">guard | medium-light skin tone | woman</annotation> + <annotation cp="💂🏼♀" type="tts">woman guard: medium-light skin tone</annotation> + <annotation cp="💂🏽♀">guard | medium skin tone | woman</annotation> + <annotation cp="💂🏽♀" type="tts">woman guard: medium skin tone</annotation> + <annotation cp="💂🏾♀">guard | medium-dark skin tone | woman</annotation> + <annotation cp="💂🏾♀" type="tts">woman guard: medium-dark skin tone</annotation> + <annotation cp="💂🏿♀">dark skin tone | guard | woman</annotation> + <annotation cp="💂🏿♀" type="tts">woman guard: dark skin tone</annotation> + <annotation cp="🥷🏻">fighter | hidden | light skin tone | ninja | stealth</annotation> + <annotation cp="🥷🏻" type="tts">ninja: light skin tone</annotation> + <annotation cp="🥷🏼">fighter | hidden | medium-light skin tone | ninja | stealth</annotation> + <annotation cp="🥷🏼" type="tts">ninja: medium-light skin tone</annotation> + <annotation cp="🥷🏽">fighter | hidden | medium skin tone | ninja | stealth</annotation> + <annotation cp="🥷🏽" type="tts">ninja: medium skin tone</annotation> + <annotation cp="🥷🏾">fighter | hidden | medium-dark skin tone | ninja | stealth</annotation> + <annotation cp="🥷🏾" type="tts">ninja: medium-dark skin tone</annotation> + <annotation cp="🥷🏿">dark skin tone | fighter | hidden | ninja | stealth</annotation> + <annotation cp="🥷🏿" type="tts">ninja: dark skin tone</annotation> + <annotation cp="👷🏻">construction | hat | light skin tone | worker</annotation> + <annotation cp="👷🏻" type="tts">construction worker: light skin tone</annotation> + <annotation cp="👷🏼">construction | hat | medium-light skin tone | worker</annotation> + <annotation cp="👷🏼" type="tts">construction worker: medium-light skin tone</annotation> + <annotation cp="👷🏽">construction | hat | medium skin tone | worker</annotation> + <annotation cp="👷🏽" type="tts">construction worker: medium skin tone</annotation> + <annotation cp="👷🏾">construction | hat | medium-dark skin tone | worker</annotation> + <annotation cp="👷🏾" type="tts">construction worker: medium-dark skin tone</annotation> + <annotation cp="👷🏿">construction | dark skin tone | hat | worker</annotation> + <annotation cp="👷🏿" type="tts">construction worker: dark skin tone</annotation> + <annotation cp="👷🏻♂">construction | light skin tone | man | worker</annotation> + <annotation cp="👷🏻♂" type="tts">man construction worker: light skin tone</annotation> + <annotation cp="👷🏼♂">construction | man | medium-light skin tone | worker</annotation> + <annotation cp="👷🏼♂" type="tts">man construction worker: medium-light skin tone</annotation> + <annotation cp="👷🏽♂">construction | man | medium skin tone | worker</annotation> + <annotation cp="👷🏽♂" type="tts">man construction worker: medium skin tone</annotation> + <annotation cp="👷🏾♂">construction | man | medium-dark skin tone | worker</annotation> + <annotation cp="👷🏾♂" type="tts">man construction worker: medium-dark skin tone</annotation> + <annotation cp="👷🏿♂">construction | dark skin tone | man | worker</annotation> + <annotation cp="👷🏿♂" type="tts">man construction worker: dark skin tone</annotation> + <annotation cp="👷🏻♀">construction | light skin tone | woman | worker</annotation> + <annotation cp="👷🏻♀" type="tts">woman construction worker: light skin tone</annotation> + <annotation cp="👷🏼♀">construction | medium-light skin tone | woman | worker</annotation> + <annotation cp="👷🏼♀" type="tts">woman construction worker: medium-light skin tone</annotation> + <annotation cp="👷🏽♀">construction | medium skin tone | woman | worker</annotation> + <annotation cp="👷🏽♀" type="tts">woman construction worker: medium skin tone</annotation> + <annotation cp="👷🏾♀">construction | medium-dark skin tone | woman | worker</annotation> + <annotation cp="👷🏾♀" type="tts">woman construction worker: medium-dark skin tone</annotation> + <annotation cp="👷🏿♀">construction | dark skin tone | woman | worker</annotation> + <annotation cp="👷🏿♀" type="tts">woman construction worker: dark skin tone</annotation> + <annotation cp="🤴🏻">light skin tone | prince</annotation> + <annotation cp="🤴🏻" type="tts">prince: light skin tone</annotation> + <annotation cp="🤴🏼">medium-light skin tone | prince</annotation> + <annotation cp="🤴🏼" type="tts">prince: medium-light skin tone</annotation> + <annotation cp="🤴🏽">medium skin tone | prince</annotation> + <annotation cp="🤴🏽" type="tts">prince: medium skin tone</annotation> + <annotation cp="🤴🏾">medium-dark skin tone | prince</annotation> + <annotation cp="🤴🏾" type="tts">prince: medium-dark skin tone</annotation> + <annotation cp="🤴🏿">dark skin tone | prince</annotation> + <annotation cp="🤴🏿" type="tts">prince: dark skin tone</annotation> + <annotation cp="👸🏻">fairy tale | fantasy | light skin tone | princess</annotation> + <annotation cp="👸🏻" type="tts">princess: light skin tone</annotation> + <annotation cp="👸🏼">fairy tale | fantasy | medium-light skin tone | princess</annotation> + <annotation cp="👸🏼" type="tts">princess: medium-light skin tone</annotation> + <annotation cp="👸🏽">fairy tale | fantasy | medium skin tone | princess</annotation> + <annotation cp="👸🏽" type="tts">princess: medium skin tone</annotation> + <annotation cp="👸🏾">fairy tale | fantasy | medium-dark skin tone | princess</annotation> + <annotation cp="👸🏾" type="tts">princess: medium-dark skin tone</annotation> + <annotation cp="👸🏿">dark skin tone | fairy tale | fantasy | princess</annotation> + <annotation cp="👸🏿" type="tts">princess: dark skin tone</annotation> + <annotation cp="👳🏻">light skin tone | person wearing turban | turban</annotation> + <annotation cp="👳🏻" type="tts">person wearing turban: light skin tone</annotation> + <annotation cp="👳🏼">medium-light skin tone | person wearing turban | turban</annotation> + <annotation cp="👳🏼" type="tts">person wearing turban: medium-light skin tone</annotation> + <annotation cp="👳🏽">medium skin tone | person wearing turban | turban</annotation> + <annotation cp="👳🏽" type="tts">person wearing turban: medium skin tone</annotation> + <annotation cp="👳🏾">medium-dark skin tone | person wearing turban | turban</annotation> + <annotation cp="👳🏾" type="tts">person wearing turban: medium-dark skin tone</annotation> + <annotation cp="👳🏿">dark skin tone | person wearing turban | turban</annotation> + <annotation cp="👳🏿" type="tts">person wearing turban: dark skin tone</annotation> + <annotation cp="👳🏻♂">light skin tone | man | man wearing turban | turban</annotation> + <annotation cp="👳🏻♂" type="tts">man wearing turban: light skin tone</annotation> + <annotation cp="👳🏼♂">man | man wearing turban | medium-light skin tone | turban</annotation> + <annotation cp="👳🏼♂" type="tts">man wearing turban: medium-light skin tone</annotation> + <annotation cp="👳🏽♂">man | man wearing turban | medium skin tone | turban</annotation> + <annotation cp="👳🏽♂" type="tts">man wearing turban: medium skin tone</annotation> + <annotation cp="👳🏾♂">man | man wearing turban | medium-dark skin tone | turban</annotation> + <annotation cp="👳🏾♂" type="tts">man wearing turban: medium-dark skin tone</annotation> + <annotation cp="👳🏿♂">dark skin tone | man | man wearing turban | turban</annotation> + <annotation cp="👳🏿♂" type="tts">man wearing turban: dark skin tone</annotation> + <annotation cp="👳🏻♀">light skin tone | turban | woman | woman wearing turban</annotation> + <annotation cp="👳🏻♀" type="tts">woman wearing turban: light skin tone</annotation> + <annotation cp="👳🏼♀">medium-light skin tone | turban | woman | woman wearing turban</annotation> + <annotation cp="👳🏼♀" type="tts">woman wearing turban: medium-light skin tone</annotation> + <annotation cp="👳🏽♀">medium skin tone | turban | woman | woman wearing turban</annotation> + <annotation cp="👳🏽♀" type="tts">woman wearing turban: medium skin tone</annotation> + <annotation cp="👳🏾♀">medium-dark skin tone | turban | woman | woman wearing turban</annotation> + <annotation cp="👳🏾♀" type="tts">woman wearing turban: medium-dark skin tone</annotation> + <annotation cp="👳🏿♀">dark skin tone | turban | woman | woman wearing turban</annotation> + <annotation cp="👳🏿♀" type="tts">woman wearing turban: dark skin tone</annotation> + <annotation cp="👲🏻">cap | gua pi mao | hat | light skin tone | person | person with skullcap | skullcap</annotation> + <annotation cp="👲🏻" type="tts">person with skullcap: light skin tone</annotation> + <annotation cp="👲🏼">cap | gua pi mao | hat | medium-light skin tone | person | person with skullcap | skullcap</annotation> + <annotation cp="👲🏼" type="tts">person with skullcap: medium-light skin tone</annotation> + <annotation cp="👲🏽">cap | gua pi mao | hat | medium skin tone | person | person with skullcap | skullcap</annotation> + <annotation cp="👲🏽" type="tts">person with skullcap: medium skin tone</annotation> + <annotation cp="👲🏾">cap | gua pi mao | hat | medium-dark skin tone | person | person with skullcap | skullcap</annotation> + <annotation cp="👲🏾" type="tts">person with skullcap: medium-dark skin tone</annotation> + <annotation cp="👲🏿">cap | dark skin tone | gua pi mao | hat | person | person with skullcap | skullcap</annotation> + <annotation cp="👲🏿" type="tts">person with skullcap: dark skin tone</annotation> + <annotation cp="🧕🏻">headscarf | hijab | light skin tone | mantilla | tichel | woman with headscarf</annotation> + <annotation cp="🧕🏻" type="tts">woman with headscarf: light skin tone</annotation> + <annotation cp="🧕🏼">headscarf | hijab | mantilla | medium-light skin tone | tichel | woman with headscarf</annotation> + <annotation cp="🧕🏼" type="tts">woman with headscarf: medium-light skin tone</annotation> + <annotation cp="🧕🏽">headscarf | hijab | mantilla | medium skin tone | tichel | woman with headscarf</annotation> + <annotation cp="🧕🏽" type="tts">woman with headscarf: medium skin tone</annotation> + <annotation cp="🧕🏾">headscarf | hijab | mantilla | medium-dark skin tone | tichel | woman with headscarf</annotation> + <annotation cp="🧕🏾" type="tts">woman with headscarf: medium-dark skin tone</annotation> + <annotation cp="🧕🏿">dark skin tone | headscarf | hijab | mantilla | tichel | woman with headscarf</annotation> + <annotation cp="🧕🏿" type="tts">woman with headscarf: dark skin tone</annotation> + <annotation cp="🤵🏻">groom | light skin tone | person | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏻" type="tts">person in tuxedo: light skin tone</annotation> + <annotation cp="🤵🏼">groom | medium-light skin tone | person | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏼" type="tts">person in tuxedo: medium-light skin tone</annotation> + <annotation cp="🤵🏽">groom | medium skin tone | person | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏽" type="tts">person in tuxedo: medium skin tone</annotation> + <annotation cp="🤵🏾">groom | medium-dark skin tone | person | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏾" type="tts">person in tuxedo: medium-dark skin tone</annotation> + <annotation cp="🤵🏿">dark skin tone | groom | person | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏿" type="tts">person in tuxedo: dark skin tone</annotation> + <annotation cp="🤵🏻♂">light skin tone | man | man in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏻♂" type="tts">man in tuxedo: light skin tone</annotation> + <annotation cp="🤵🏼♂">man | man in tuxedo | medium-light skin tone | tuxedo</annotation> + <annotation cp="🤵🏼♂" type="tts">man in tuxedo: medium-light skin tone</annotation> + <annotation cp="🤵🏽♂">man | man in tuxedo | medium skin tone | tuxedo</annotation> + <annotation cp="🤵🏽♂" type="tts">man in tuxedo: medium skin tone</annotation> + <annotation cp="🤵🏾♂">man | man in tuxedo | medium-dark skin tone | tuxedo</annotation> + <annotation cp="🤵🏾♂" type="tts">man in tuxedo: medium-dark skin tone</annotation> + <annotation cp="🤵🏿♂">dark skin tone | man | man in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏿♂" type="tts">man in tuxedo: dark skin tone</annotation> + <annotation cp="🤵🏻♀">light skin tone | tuxedo | woman | woman in tuxedo</annotation> + <annotation cp="🤵🏻♀" type="tts">woman in tuxedo: light skin tone</annotation> + <annotation cp="🤵🏼♀">medium-light skin tone | tuxedo | woman | woman in tuxedo</annotation> + <annotation cp="🤵🏼♀" type="tts">woman in tuxedo: medium-light skin tone</annotation> + <annotation cp="🤵🏽♀">medium skin tone | tuxedo | woman | woman in tuxedo</annotation> + <annotation cp="🤵🏽♀" type="tts">woman in tuxedo: medium skin tone</annotation> + <annotation cp="🤵🏾♀">medium-dark skin tone | tuxedo | woman | woman in tuxedo</annotation> + <annotation cp="🤵🏾♀" type="tts">woman in tuxedo: medium-dark skin tone</annotation> + <annotation cp="🤵🏿♀">dark skin tone | tuxedo | woman | woman in tuxedo</annotation> + <annotation cp="🤵🏿♀" type="tts">woman in tuxedo: dark skin tone</annotation> + <annotation cp="👰🏻">bride | light skin tone | person | person with veil | veil | wedding</annotation> + <annotation cp="👰🏻" type="tts">person with veil: light skin tone</annotation> + <annotation cp="👰🏼">bride | medium-light skin tone | person | person with veil | veil | wedding</annotation> + <annotation cp="👰🏼" type="tts">person with veil: medium-light skin tone</annotation> + <annotation cp="👰🏽">bride | medium skin tone | person | person with veil | veil | wedding</annotation> + <annotation cp="👰🏽" type="tts">person with veil: medium skin tone</annotation> + <annotation cp="👰🏾">bride | medium-dark skin tone | person | person with veil | veil | wedding</annotation> + <annotation cp="👰🏾" type="tts">person with veil: medium-dark skin tone</annotation> + <annotation cp="👰🏿">bride | dark skin tone | person | person with veil | veil | wedding</annotation> + <annotation cp="👰🏿" type="tts">person with veil: dark skin tone</annotation> + <annotation cp="👰🏻♂">light skin tone | man | man with veil | veil</annotation> + <annotation cp="👰🏻♂" type="tts">man with veil: light skin tone</annotation> + <annotation cp="👰🏼♂">man | man with veil | medium-light skin tone | veil</annotation> + <annotation cp="👰🏼♂" type="tts">man with veil: medium-light skin tone</annotation> + <annotation cp="👰🏽♂">man | man with veil | medium skin tone | veil</annotation> + <annotation cp="👰🏽♂" type="tts">man with veil: medium skin tone</annotation> + <annotation cp="👰🏾♂">man | man with veil | medium-dark skin tone | veil</annotation> + <annotation cp="👰🏾♂" type="tts">man with veil: medium-dark skin tone</annotation> + <annotation cp="👰🏿♂">dark skin tone | man | man with veil | veil</annotation> + <annotation cp="👰🏿♂" type="tts">man with veil: dark skin tone</annotation> + <annotation cp="👰🏻♀">light skin tone | veil | woman | woman with veil</annotation> + <annotation cp="👰🏻♀" type="tts">woman with veil: light skin tone</annotation> + <annotation cp="👰🏼♀">medium-light skin tone | veil | woman | woman with veil</annotation> + <annotation cp="👰🏼♀" type="tts">woman with veil: medium-light skin tone</annotation> + <annotation cp="👰🏽♀">medium skin tone | veil | woman | woman with veil</annotation> + <annotation cp="👰🏽♀" type="tts">woman with veil: medium skin tone</annotation> + <annotation cp="👰🏾♀">medium-dark skin tone | veil | woman | woman with veil</annotation> + <annotation cp="👰🏾♀" type="tts">woman with veil: medium-dark skin tone</annotation> + <annotation cp="👰🏿♀">dark skin tone | veil | woman | woman with veil</annotation> + <annotation cp="👰🏿♀" type="tts">woman with veil: dark skin tone</annotation> + <annotation cp="🤰🏻">light skin tone | pregnant | woman</annotation> + <annotation cp="🤰🏻" type="tts">pregnant woman: light skin tone</annotation> + <annotation cp="🤰🏼">medium-light skin tone | pregnant | woman</annotation> + <annotation cp="🤰🏼" type="tts">pregnant woman: medium-light skin tone</annotation> + <annotation cp="🤰🏽">medium skin tone | pregnant | woman</annotation> + <annotation cp="🤰🏽" type="tts">pregnant woman: medium skin tone</annotation> + <annotation cp="🤰🏾">medium-dark skin tone | pregnant | woman</annotation> + <annotation cp="🤰🏾" type="tts">pregnant woman: medium-dark skin tone</annotation> + <annotation cp="🤰🏿">dark skin tone | pregnant | woman</annotation> + <annotation cp="🤰🏿" type="tts">pregnant woman: dark skin tone</annotation> + <annotation cp="🤱🏻">baby | breast | breast-feeding | light skin tone | nursing</annotation> + <annotation cp="🤱🏻" type="tts">breast-feeding: light skin tone</annotation> + <annotation cp="🤱🏼">baby | breast | breast-feeding | medium-light skin tone | nursing</annotation> + <annotation cp="🤱🏼" type="tts">breast-feeding: medium-light skin tone</annotation> + <annotation cp="🤱🏽">baby | breast | breast-feeding | medium skin tone | nursing</annotation> + <annotation cp="🤱🏽" type="tts">breast-feeding: medium skin tone</annotation> + <annotation cp="🤱🏾">baby | breast | breast-feeding | medium-dark skin tone | nursing</annotation> + <annotation cp="🤱🏾" type="tts">breast-feeding: medium-dark skin tone</annotation> + <annotation cp="🤱🏿">baby | breast | breast-feeding | dark skin tone | nursing</annotation> + <annotation cp="🤱🏿" type="tts">breast-feeding: dark skin tone</annotation> + <annotation cp="👩🏻🍼">baby | feeding | light skin tone | nursing | woman</annotation> + <annotation cp="👩🏻🍼" type="tts">woman feeding baby: light skin tone</annotation> + <annotation cp="👩🏼🍼">baby | feeding | medium-light skin tone | nursing | woman</annotation> + <annotation cp="👩🏼🍼" type="tts">woman feeding baby: medium-light skin tone</annotation> + <annotation cp="👩🏽🍼">baby | feeding | medium skin tone | nursing | woman</annotation> + <annotation cp="👩🏽🍼" type="tts">woman feeding baby: medium skin tone</annotation> + <annotation cp="👩🏾🍼">baby | feeding | medium-dark skin tone | nursing | woman</annotation> + <annotation cp="👩🏾🍼" type="tts">woman feeding baby: medium-dark skin tone</annotation> + <annotation cp="👩🏿🍼">baby | dark skin tone | feeding | nursing | woman</annotation> + <annotation cp="👩🏿🍼" type="tts">woman feeding baby: dark skin tone</annotation> + <annotation cp="👨🏻🍼">baby | feeding | light skin tone | man | nursing</annotation> + <annotation cp="👨🏻🍼" type="tts">man feeding baby: light skin tone</annotation> + <annotation cp="👨🏼🍼">baby | feeding | man | medium-light skin tone | nursing</annotation> + <annotation cp="👨🏼🍼" type="tts">man feeding baby: medium-light skin tone</annotation> + <annotation cp="👨🏽🍼">baby | feeding | man | medium skin tone | nursing</annotation> + <annotation cp="👨🏽🍼" type="tts">man feeding baby: medium skin tone</annotation> + <annotation cp="👨🏾🍼">baby | feeding | man | medium-dark skin tone | nursing</annotation> + <annotation cp="👨🏾🍼" type="tts">man feeding baby: medium-dark skin tone</annotation> + <annotation cp="👨🏿🍼">baby | dark skin tone | feeding | man | nursing</annotation> + <annotation cp="👨🏿🍼" type="tts">man feeding baby: dark skin tone</annotation> + <annotation cp="🧑🏻🍼">baby | feeding | light skin tone | nursing | person</annotation> + <annotation cp="🧑🏻🍼" type="tts">person feeding baby: light skin tone</annotation> + <annotation cp="🧑🏼🍼">baby | feeding | medium-light skin tone | nursing | person</annotation> + <annotation cp="🧑🏼🍼" type="tts">person feeding baby: medium-light skin tone</annotation> + <annotation cp="🧑🏽🍼">baby | feeding | medium skin tone | nursing | person</annotation> + <annotation cp="🧑🏽🍼" type="tts">person feeding baby: medium skin tone</annotation> + <annotation cp="🧑🏾🍼">baby | feeding | medium-dark skin tone | nursing | person</annotation> + <annotation cp="🧑🏾🍼" type="tts">person feeding baby: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🍼">baby | dark skin tone | feeding | nursing | person</annotation> + <annotation cp="🧑🏿🍼" type="tts">person feeding baby: dark skin tone</annotation> + <annotation cp="👼🏻">angel | baby | face | fairy tale | fantasy | light skin tone</annotation> + <annotation cp="👼🏻" type="tts">baby angel: light skin tone</annotation> + <annotation cp="👼🏼">angel | baby | face | fairy tale | fantasy | medium-light skin tone</annotation> + <annotation cp="👼🏼" type="tts">baby angel: medium-light skin tone</annotation> + <annotation cp="👼🏽">angel | baby | face | fairy tale | fantasy | medium skin tone</annotation> + <annotation cp="👼🏽" type="tts">baby angel: medium skin tone</annotation> + <annotation cp="👼🏾">angel | baby | face | fairy tale | fantasy | medium-dark skin tone</annotation> + <annotation cp="👼🏾" type="tts">baby angel: medium-dark skin tone</annotation> + <annotation cp="👼🏿">angel | baby | dark skin tone | face | fairy tale | fantasy</annotation> + <annotation cp="👼🏿" type="tts">baby angel: dark skin tone</annotation> + <annotation cp="🎅🏻">celebration | Christmas | claus | father | light skin tone | santa | Santa Claus</annotation> + <annotation cp="🎅🏻" type="tts">Santa Claus: light skin tone</annotation> + <annotation cp="🎅🏼">celebration | Christmas | claus | father | medium-light skin tone | santa | Santa Claus</annotation> + <annotation cp="🎅🏼" type="tts">Santa Claus: medium-light skin tone</annotation> + <annotation cp="🎅🏽">celebration | Christmas | claus | father | medium skin tone | santa | Santa Claus</annotation> + <annotation cp="🎅🏽" type="tts">Santa Claus: medium skin tone</annotation> + <annotation cp="🎅🏾">celebration | Christmas | claus | father | medium-dark skin tone | santa | Santa Claus</annotation> + <annotation cp="🎅🏾" type="tts">Santa Claus: medium-dark skin tone</annotation> + <annotation cp="🎅🏿">celebration | Christmas | claus | dark skin tone | father | santa | Santa Claus</annotation> + <annotation cp="🎅🏿" type="tts">Santa Claus: dark skin tone</annotation> + <annotation cp="🤶🏻">celebration | Christmas | claus | light skin tone | mother | Mrs. | Mrs. Claus</annotation> + <annotation cp="🤶🏻" type="tts">Mrs. Claus: light skin tone</annotation> + <annotation cp="🤶🏼">celebration | Christmas | claus | medium-light skin tone | mother | Mrs. | Mrs. Claus</annotation> + <annotation cp="🤶🏼" type="tts">Mrs. Claus: medium-light skin tone</annotation> + <annotation cp="🤶🏽">celebration | Christmas | claus | medium skin tone | mother | Mrs. | Mrs. Claus</annotation> + <annotation cp="🤶🏽" type="tts">Mrs. Claus: medium skin tone</annotation> + <annotation cp="🤶🏾">celebration | Christmas | claus | medium-dark skin tone | mother | Mrs. | Mrs. Claus</annotation> + <annotation cp="🤶🏾" type="tts">Mrs. Claus: medium-dark skin tone</annotation> + <annotation cp="🤶🏿">celebration | Christmas | claus | dark skin tone | mother | Mrs. | Mrs. Claus</annotation> + <annotation cp="🤶🏿" type="tts">Mrs. Claus: dark skin tone</annotation> + <annotation cp="🧑🏻🎄">Claus, christmas | light skin tone | mx claus</annotation> + <annotation cp="🧑🏻🎄" type="tts">mx claus: light skin tone</annotation> + <annotation cp="🧑🏼🎄">Claus, christmas | medium-light skin tone | mx claus</annotation> + <annotation cp="🧑🏼🎄" type="tts">mx claus: medium-light skin tone</annotation> + <annotation cp="🧑🏽🎄">Claus, christmas | medium skin tone | mx claus</annotation> + <annotation cp="🧑🏽🎄" type="tts">mx claus: medium skin tone</annotation> + <annotation cp="🧑🏾🎄">Claus, christmas | medium-dark skin tone | mx claus</annotation> + <annotation cp="🧑🏾🎄" type="tts">mx claus: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🎄">Claus, christmas | dark skin tone | mx claus</annotation> + <annotation cp="🧑🏿🎄" type="tts">mx claus: dark skin tone</annotation> + <annotation cp="🦸🏻">good | hero | heroine | light skin tone | superhero | superpower</annotation> + <annotation cp="🦸🏻" type="tts">superhero: light skin tone</annotation> + <annotation cp="🦸🏼">good | hero | heroine | medium-light skin tone | superhero | superpower</annotation> + <annotation cp="🦸🏼" type="tts">superhero: medium-light skin tone</annotation> + <annotation cp="🦸🏽">good | hero | heroine | medium skin tone | superhero | superpower</annotation> + <annotation cp="🦸🏽" type="tts">superhero: medium skin tone</annotation> + <annotation cp="🦸🏾">good | hero | heroine | medium-dark skin tone | superhero | superpower</annotation> + <annotation cp="🦸🏾" type="tts">superhero: medium-dark skin tone</annotation> + <annotation cp="🦸🏿">dark skin tone | good | hero | heroine | superhero | superpower</annotation> + <annotation cp="🦸🏿" type="tts">superhero: dark skin tone</annotation> + <annotation cp="🦸🏻♂">good | hero | light skin tone | man | man superhero | superpower</annotation> + <annotation cp="🦸🏻♂" type="tts">man superhero: light skin tone</annotation> + <annotation cp="🦸🏼♂">good | hero | man | man superhero | medium-light skin tone | superpower</annotation> + <annotation cp="🦸🏼♂" type="tts">man superhero: medium-light skin tone</annotation> + <annotation cp="🦸🏽♂">good | hero | man | man superhero | medium skin tone | superpower</annotation> + <annotation cp="🦸🏽♂" type="tts">man superhero: medium skin tone</annotation> + <annotation cp="🦸🏾♂">good | hero | man | man superhero | medium-dark skin tone | superpower</annotation> + <annotation cp="🦸🏾♂" type="tts">man superhero: medium-dark skin tone</annotation> + <annotation cp="🦸🏿♂">dark skin tone | good | hero | man | man superhero | superpower</annotation> + <annotation cp="🦸🏿♂" type="tts">man superhero: dark skin tone</annotation> + <annotation cp="🦸🏻♀">good | hero | heroine | light skin tone | superpower | woman | woman superhero</annotation> + <annotation cp="🦸🏻♀" type="tts">woman superhero: light skin tone</annotation> + <annotation cp="🦸🏼♀">good | hero | heroine | medium-light skin tone | superpower | woman | woman superhero</annotation> + <annotation cp="🦸🏼♀" type="tts">woman superhero: medium-light skin tone</annotation> + <annotation cp="🦸🏽♀">good | hero | heroine | medium skin tone | superpower | woman | woman superhero</annotation> + <annotation cp="🦸🏽♀" type="tts">woman superhero: medium skin tone</annotation> + <annotation cp="🦸🏾♀">good | hero | heroine | medium-dark skin tone | superpower | woman | woman superhero</annotation> + <annotation cp="🦸🏾♀" type="tts">woman superhero: medium-dark skin tone</annotation> + <annotation cp="🦸🏿♀">dark skin tone | good | hero | heroine | superpower | woman | woman superhero</annotation> + <annotation cp="🦸🏿♀" type="tts">woman superhero: dark skin tone</annotation> + <annotation cp="🦹🏻">criminal | evil | light skin tone | superpower | supervillain | villain</annotation> + <annotation cp="🦹🏻" type="tts">supervillain: light skin tone</annotation> + <annotation cp="🦹🏼">criminal | evil | medium-light skin tone | superpower | supervillain | villain</annotation> + <annotation cp="🦹🏼" type="tts">supervillain: medium-light skin tone</annotation> + <annotation cp="🦹🏽">criminal | evil | medium skin tone | superpower | supervillain | villain</annotation> + <annotation cp="🦹🏽" type="tts">supervillain: medium skin tone</annotation> + <annotation cp="🦹🏾">criminal | evil | medium-dark skin tone | superpower | supervillain | villain</annotation> + <annotation cp="🦹🏾" type="tts">supervillain: medium-dark skin tone</annotation> + <annotation cp="🦹🏿">criminal | dark skin tone | evil | superpower | supervillain | villain</annotation> + <annotation cp="🦹🏿" type="tts">supervillain: dark skin tone</annotation> + <annotation cp="🦹🏻♂">criminal | evil | light skin tone | man | man supervillain | superpower | villain</annotation> + <annotation cp="🦹🏻♂" type="tts">man supervillain: light skin tone</annotation> + <annotation cp="🦹🏼♂">criminal | evil | man | man supervillain | medium-light skin tone | superpower | villain</annotation> + <annotation cp="🦹🏼♂" type="tts">man supervillain: medium-light skin tone</annotation> + <annotation cp="🦹🏽♂">criminal | evil | man | man supervillain | medium skin tone | superpower | villain</annotation> + <annotation cp="🦹🏽♂" type="tts">man supervillain: medium skin tone</annotation> + <annotation cp="🦹🏾♂">criminal | evil | man | man supervillain | medium-dark skin tone | superpower | villain</annotation> + <annotation cp="🦹🏾♂" type="tts">man supervillain: medium-dark skin tone</annotation> + <annotation cp="🦹🏿♂">criminal | dark skin tone | evil | man | man supervillain | superpower | villain</annotation> + <annotation cp="🦹🏿♂" type="tts">man supervillain: dark skin tone</annotation> + <annotation cp="🦹🏻♀">criminal | evil | light skin tone | superpower | villain | woman | woman supervillain</annotation> + <annotation cp="🦹🏻♀" type="tts">woman supervillain: light skin tone</annotation> + <annotation cp="🦹🏼♀">criminal | evil | medium-light skin tone | superpower | villain | woman | woman supervillain</annotation> + <annotation cp="🦹🏼♀" type="tts">woman supervillain: medium-light skin tone</annotation> + <annotation cp="🦹🏽♀">criminal | evil | medium skin tone | superpower | villain | woman | woman supervillain</annotation> + <annotation cp="🦹🏽♀" type="tts">woman supervillain: medium skin tone</annotation> + <annotation cp="🦹🏾♀">criminal | evil | medium-dark skin tone | superpower | villain | woman | woman supervillain</annotation> + <annotation cp="🦹🏾♀" type="tts">woman supervillain: medium-dark skin tone</annotation> + <annotation cp="🦹🏿♀">criminal | dark skin tone | evil | superpower | villain | woman | woman supervillain</annotation> + <annotation cp="🦹🏿♀" type="tts">woman supervillain: dark skin tone</annotation> + <annotation cp="🧙🏻">light skin tone | mage | sorcerer | sorceress | witch | wizard</annotation> + <annotation cp="🧙🏻" type="tts">mage: light skin tone</annotation> + <annotation cp="🧙🏼">mage | medium-light skin tone | sorcerer | sorceress | witch | wizard</annotation> + <annotation cp="🧙🏼" type="tts">mage: medium-light skin tone</annotation> + <annotation cp="🧙🏽">mage | medium skin tone | sorcerer | sorceress | witch | wizard</annotation> + <annotation cp="🧙🏽" type="tts">mage: medium skin tone</annotation> + <annotation cp="🧙🏾">mage | medium-dark skin tone | sorcerer | sorceress | witch | wizard</annotation> + <annotation cp="🧙🏾" type="tts">mage: medium-dark skin tone</annotation> + <annotation cp="🧙🏿">dark skin tone | mage | sorcerer | sorceress | witch | wizard</annotation> + <annotation cp="🧙🏿" type="tts">mage: dark skin tone</annotation> + <annotation cp="🧙🏻♂">light skin tone | man mage | sorcerer | wizard</annotation> + <annotation cp="🧙🏻♂" type="tts">man mage: light skin tone</annotation> + <annotation cp="🧙🏼♂">man mage | medium-light skin tone | sorcerer | wizard</annotation> + <annotation cp="🧙🏼♂" type="tts">man mage: medium-light skin tone</annotation> + <annotation cp="🧙🏽♂">man mage | medium skin tone | sorcerer | wizard</annotation> + <annotation cp="🧙🏽♂" type="tts">man mage: medium skin tone</annotation> + <annotation cp="🧙🏾♂">man mage | medium-dark skin tone | sorcerer | wizard</annotation> + <annotation cp="🧙🏾♂" type="tts">man mage: medium-dark skin tone</annotation> + <annotation cp="🧙🏿♂">dark skin tone | man mage | sorcerer | wizard</annotation> + <annotation cp="🧙🏿♂" type="tts">man mage: dark skin tone</annotation> + <annotation cp="🧙🏻♀">light skin tone | sorceress | witch | woman mage</annotation> + <annotation cp="🧙🏻♀" type="tts">woman mage: light skin tone</annotation> + <annotation cp="🧙🏼♀">medium-light skin tone | sorceress | witch | woman mage</annotation> + <annotation cp="🧙🏼♀" type="tts">woman mage: medium-light skin tone</annotation> + <annotation cp="🧙🏽♀">medium skin tone | sorceress | witch | woman mage</annotation> + <annotation cp="🧙🏽♀" type="tts">woman mage: medium skin tone</annotation> + <annotation cp="🧙🏾♀">medium-dark skin tone | sorceress | witch | woman mage</annotation> + <annotation cp="🧙🏾♀" type="tts">woman mage: medium-dark skin tone</annotation> + <annotation cp="🧙🏿♀">dark skin tone | sorceress | witch | woman mage</annotation> + <annotation cp="🧙🏿♀" type="tts">woman mage: dark skin tone</annotation> + <annotation cp="🧚🏻">fairy | light skin tone | Oberon | Puck | Titania</annotation> + <annotation cp="🧚🏻" type="tts">fairy: light skin tone</annotation> + <annotation cp="🧚🏼">fairy | medium-light skin tone | Oberon | Puck | Titania</annotation> + <annotation cp="🧚🏼" type="tts">fairy: medium-light skin tone</annotation> + <annotation cp="🧚🏽">fairy | medium skin tone | Oberon | Puck | Titania</annotation> + <annotation cp="🧚🏽" type="tts">fairy: medium skin tone</annotation> + <annotation cp="🧚🏾">fairy | medium-dark skin tone | Oberon | Puck | Titania</annotation> + <annotation cp="🧚🏾" type="tts">fairy: medium-dark skin tone</annotation> + <annotation cp="🧚🏿">dark skin tone | fairy | Oberon | Puck | Titania</annotation> + <annotation cp="🧚🏿" type="tts">fairy: dark skin tone</annotation> + <annotation cp="🧚🏻♂">light skin tone | man fairy | Oberon | Puck</annotation> + <annotation cp="🧚🏻♂" type="tts">man fairy: light skin tone</annotation> + <annotation cp="🧚🏼♂">man fairy | medium-light skin tone | Oberon | Puck</annotation> + <annotation cp="🧚🏼♂" type="tts">man fairy: medium-light skin tone</annotation> + <annotation cp="🧚🏽♂">man fairy | medium skin tone | Oberon | Puck</annotation> + <annotation cp="🧚🏽♂" type="tts">man fairy: medium skin tone</annotation> + <annotation cp="🧚🏾♂">man fairy | medium-dark skin tone | Oberon | Puck</annotation> + <annotation cp="🧚🏾♂" type="tts">man fairy: medium-dark skin tone</annotation> + <annotation cp="🧚🏿♂">dark skin tone | man fairy | Oberon | Puck</annotation> + <annotation cp="🧚🏿♂" type="tts">man fairy: dark skin tone</annotation> + <annotation cp="🧚🏻♀">light skin tone | Titania | woman fairy</annotation> + <annotation cp="🧚🏻♀" type="tts">woman fairy: light skin tone</annotation> + <annotation cp="🧚🏼♀">medium-light skin tone | Titania | woman fairy</annotation> + <annotation cp="🧚🏼♀" type="tts">woman fairy: medium-light skin tone</annotation> + <annotation cp="🧚🏽♀">medium skin tone | Titania | woman fairy</annotation> + <annotation cp="🧚🏽♀" type="tts">woman fairy: medium skin tone</annotation> + <annotation cp="🧚🏾♀">medium-dark skin tone | Titania | woman fairy</annotation> + <annotation cp="🧚🏾♀" type="tts">woman fairy: medium-dark skin tone</annotation> + <annotation cp="🧚🏿♀">dark skin tone | Titania | woman fairy</annotation> + <annotation cp="🧚🏿♀" type="tts">woman fairy: dark skin tone</annotation> + <annotation cp="🧛🏻">Dracula | light skin tone | undead | vampire</annotation> + <annotation cp="🧛🏻" type="tts">vampire: light skin tone</annotation> + <annotation cp="🧛🏼">Dracula | medium-light skin tone | undead | vampire</annotation> + <annotation cp="🧛🏼" type="tts">vampire: medium-light skin tone</annotation> + <annotation cp="🧛🏽">Dracula | medium skin tone | undead | vampire</annotation> + <annotation cp="🧛🏽" type="tts">vampire: medium skin tone</annotation> + <annotation cp="🧛🏾">Dracula | medium-dark skin tone | undead | vampire</annotation> + <annotation cp="🧛🏾" type="tts">vampire: medium-dark skin tone</annotation> + <annotation cp="🧛🏿">dark skin tone | Dracula | undead | vampire</annotation> + <annotation cp="🧛🏿" type="tts">vampire: dark skin tone</annotation> + <annotation cp="🧛🏻♂">Dracula | light skin tone | man vampire | undead</annotation> + <annotation cp="🧛🏻♂" type="tts">man vampire: light skin tone</annotation> + <annotation cp="🧛🏼♂">Dracula | man vampire | medium-light skin tone | undead</annotation> + <annotation cp="🧛🏼♂" type="tts">man vampire: medium-light skin tone</annotation> + <annotation cp="🧛🏽♂">Dracula | man vampire | medium skin tone | undead</annotation> + <annotation cp="🧛🏽♂" type="tts">man vampire: medium skin tone</annotation> + <annotation cp="🧛🏾♂">Dracula | man vampire | medium-dark skin tone | undead</annotation> + <annotation cp="🧛🏾♂" type="tts">man vampire: medium-dark skin tone</annotation> + <annotation cp="🧛🏿♂">dark skin tone | Dracula | man vampire | undead</annotation> + <annotation cp="🧛🏿♂" type="tts">man vampire: dark skin tone</annotation> + <annotation cp="🧛🏻♀">light skin tone | undead | woman vampire</annotation> + <annotation cp="🧛🏻♀" type="tts">woman vampire: light skin tone</annotation> + <annotation cp="🧛🏼♀">medium-light skin tone | undead | woman vampire</annotation> + <annotation cp="🧛🏼♀" type="tts">woman vampire: medium-light skin tone</annotation> + <annotation cp="🧛🏽♀">medium skin tone | undead | woman vampire</annotation> + <annotation cp="🧛🏽♀" type="tts">woman vampire: medium skin tone</annotation> + <annotation cp="🧛🏾♀">medium-dark skin tone | undead | woman vampire</annotation> + <annotation cp="🧛🏾♀" type="tts">woman vampire: medium-dark skin tone</annotation> + <annotation cp="🧛🏿♀">dark skin tone | undead | woman vampire</annotation> + <annotation cp="🧛🏿♀" type="tts">woman vampire: dark skin tone</annotation> + <annotation cp="🧜🏻">light skin tone | mermaid | merman | merperson | merwoman</annotation> + <annotation cp="🧜🏻" type="tts">merperson: light skin tone</annotation> + <annotation cp="🧜🏼">medium-light skin tone | mermaid | merman | merperson | merwoman</annotation> + <annotation cp="🧜🏼" type="tts">merperson: medium-light skin tone</annotation> + <annotation cp="🧜🏽">medium skin tone | mermaid | merman | merperson | merwoman</annotation> + <annotation cp="🧜🏽" type="tts">merperson: medium skin tone</annotation> + <annotation cp="🧜🏾">medium-dark skin tone | mermaid | merman | merperson | merwoman</annotation> + <annotation cp="🧜🏾" type="tts">merperson: medium-dark skin tone</annotation> + <annotation cp="🧜🏿">dark skin tone | mermaid | merman | merperson | merwoman</annotation> + <annotation cp="🧜🏿" type="tts">merperson: dark skin tone</annotation> + <annotation cp="🧜🏻♂">light skin tone | merman | Triton</annotation> + <annotation cp="🧜🏻♂" type="tts">merman: light skin tone</annotation> + <annotation cp="🧜🏼♂">medium-light skin tone | merman | Triton</annotation> + <annotation cp="🧜🏼♂" type="tts">merman: medium-light skin tone</annotation> + <annotation cp="🧜🏽♂">medium skin tone | merman | Triton</annotation> + <annotation cp="🧜🏽♂" type="tts">merman: medium skin tone</annotation> + <annotation cp="🧜🏾♂">medium-dark skin tone | merman | Triton</annotation> + <annotation cp="🧜🏾♂" type="tts">merman: medium-dark skin tone</annotation> + <annotation cp="🧜🏿♂">dark skin tone | merman | Triton</annotation> + <annotation cp="🧜🏿♂" type="tts">merman: dark skin tone</annotation> + <annotation cp="🧜🏻♀">light skin tone | mermaid | merwoman</annotation> + <annotation cp="🧜🏻♀" type="tts">mermaid: light skin tone</annotation> + <annotation cp="🧜🏼♀">medium-light skin tone | mermaid | merwoman</annotation> + <annotation cp="🧜🏼♀" type="tts">mermaid: medium-light skin tone</annotation> + <annotation cp="🧜🏽♀">medium skin tone | mermaid | merwoman</annotation> + <annotation cp="🧜🏽♀" type="tts">mermaid: medium skin tone</annotation> + <annotation cp="🧜🏾♀">medium-dark skin tone | mermaid | merwoman</annotation> + <annotation cp="🧜🏾♀" type="tts">mermaid: medium-dark skin tone</annotation> + <annotation cp="🧜🏿♀">dark skin tone | mermaid | merwoman</annotation> + <annotation cp="🧜🏿♀" type="tts">mermaid: dark skin tone</annotation> + <annotation cp="🧝🏻">elf | light skin tone | magical</annotation> + <annotation cp="🧝🏻" type="tts">elf: light skin tone</annotation> + <annotation cp="🧝🏼">elf | magical | medium-light skin tone</annotation> + <annotation cp="🧝🏼" type="tts">elf: medium-light skin tone</annotation> + <annotation cp="🧝🏽">elf | magical | medium skin tone</annotation> + <annotation cp="🧝🏽" type="tts">elf: medium skin tone</annotation> + <annotation cp="🧝🏾">elf | magical | medium-dark skin tone</annotation> + <annotation cp="🧝🏾" type="tts">elf: medium-dark skin tone</annotation> + <annotation cp="🧝🏿">dark skin tone | elf | magical</annotation> + <annotation cp="🧝🏿" type="tts">elf: dark skin tone</annotation> + <annotation cp="🧝🏻♂">light skin tone | magical | man elf</annotation> + <annotation cp="🧝🏻♂" type="tts">man elf: light skin tone</annotation> + <annotation cp="🧝🏼♂">magical | man elf | medium-light skin tone</annotation> + <annotation cp="🧝🏼♂" type="tts">man elf: medium-light skin tone</annotation> + <annotation cp="🧝🏽♂">magical | man elf | medium skin tone</annotation> + <annotation cp="🧝🏽♂" type="tts">man elf: medium skin tone</annotation> + <annotation cp="🧝🏾♂">magical | man elf | medium-dark skin tone</annotation> + <annotation cp="🧝🏾♂" type="tts">man elf: medium-dark skin tone</annotation> + <annotation cp="🧝🏿♂">dark skin tone | magical | man elf</annotation> + <annotation cp="🧝🏿♂" type="tts">man elf: dark skin tone</annotation> + <annotation cp="🧝🏻♀">light skin tone | magical | woman elf</annotation> + <annotation cp="🧝🏻♀" type="tts">woman elf: light skin tone</annotation> + <annotation cp="🧝🏼♀">magical | medium-light skin tone | woman elf</annotation> + <annotation cp="🧝🏼♀" type="tts">woman elf: medium-light skin tone</annotation> + <annotation cp="🧝🏽♀">magical | medium skin tone | woman elf</annotation> + <annotation cp="🧝🏽♀" type="tts">woman elf: medium skin tone</annotation> + <annotation cp="🧝🏾♀">magical | medium-dark skin tone | woman elf</annotation> + <annotation cp="🧝🏾♀" type="tts">woman elf: medium-dark skin tone</annotation> + <annotation cp="🧝🏿♀">dark skin tone | magical | woman elf</annotation> + <annotation cp="🧝🏿♀" type="tts">woman elf: dark skin tone</annotation> + <annotation cp="💆🏻">face | light skin tone | massage | person getting massage | salon</annotation> + <annotation cp="💆🏻" type="tts">person getting massage: light skin tone</annotation> + <annotation cp="💆🏼">face | massage | medium-light skin tone | person getting massage | salon</annotation> + <annotation cp="💆🏼" type="tts">person getting massage: medium-light skin tone</annotation> + <annotation cp="💆🏽">face | massage | medium skin tone | person getting massage | salon</annotation> + <annotation cp="💆🏽" type="tts">person getting massage: medium skin tone</annotation> + <annotation cp="💆🏾">face | massage | medium-dark skin tone | person getting massage | salon</annotation> + <annotation cp="💆🏾" type="tts">person getting massage: medium-dark skin tone</annotation> + <annotation cp="💆🏿">dark skin tone | face | massage | person getting massage | salon</annotation> + <annotation cp="💆🏿" type="tts">person getting massage: dark skin tone</annotation> + <annotation cp="💆🏻♂">face | light skin tone | man | man getting massage | massage</annotation> + <annotation cp="💆🏻♂" type="tts">man getting massage: light skin tone</annotation> + <annotation cp="💆🏼♂">face | man | man getting massage | massage | medium-light skin tone</annotation> + <annotation cp="💆🏼♂" type="tts">man getting massage: medium-light skin tone</annotation> + <annotation cp="💆🏽♂">face | man | man getting massage | massage | medium skin tone</annotation> + <annotation cp="💆🏽♂" type="tts">man getting massage: medium skin tone</annotation> + <annotation cp="💆🏾♂">face | man | man getting massage | massage | medium-dark skin tone</annotation> + <annotation cp="💆🏾♂" type="tts">man getting massage: medium-dark skin tone</annotation> + <annotation cp="💆🏿♂">dark skin tone | face | man | man getting massage | massage</annotation> + <annotation cp="💆🏿♂" type="tts">man getting massage: dark skin tone</annotation> + <annotation cp="💆🏻♀">face | light skin tone | massage | woman | woman getting massage</annotation> + <annotation cp="💆🏻♀" type="tts">woman getting massage: light skin tone</annotation> + <annotation cp="💆🏼♀">face | massage | medium-light skin tone | woman | woman getting massage</annotation> + <annotation cp="💆🏼♀" type="tts">woman getting massage: medium-light skin tone</annotation> + <annotation cp="💆🏽♀">face | massage | medium skin tone | woman | woman getting massage</annotation> + <annotation cp="💆🏽♀" type="tts">woman getting massage: medium skin tone</annotation> + <annotation cp="💆🏾♀">face | massage | medium-dark skin tone | woman | woman getting massage</annotation> + <annotation cp="💆🏾♀" type="tts">woman getting massage: medium-dark skin tone</annotation> + <annotation cp="💆🏿♀">dark skin tone | face | massage | woman | woman getting massage</annotation> + <annotation cp="💆🏿♀" type="tts">woman getting massage: dark skin tone</annotation> + <annotation cp="💇🏻">barber | beauty | haircut | light skin tone | parlor | person getting haircut</annotation> + <annotation cp="💇🏻" type="tts">person getting haircut: light skin tone</annotation> + <annotation cp="💇🏼">barber | beauty | haircut | medium-light skin tone | parlor | person getting haircut</annotation> + <annotation cp="💇🏼" type="tts">person getting haircut: medium-light skin tone</annotation> + <annotation cp="💇🏽">barber | beauty | haircut | medium skin tone | parlor | person getting haircut</annotation> + <annotation cp="💇🏽" type="tts">person getting haircut: medium skin tone</annotation> + <annotation cp="💇🏾">barber | beauty | haircut | medium-dark skin tone | parlor | person getting haircut</annotation> + <annotation cp="💇🏾" type="tts">person getting haircut: medium-dark skin tone</annotation> + <annotation cp="💇🏿">barber | beauty | dark skin tone | haircut | parlor | person getting haircut</annotation> + <annotation cp="💇🏿" type="tts">person getting haircut: dark skin tone</annotation> + <annotation cp="💇🏻♂">haircut | light skin tone | man | man getting haircut</annotation> + <annotation cp="💇🏻♂" type="tts">man getting haircut: light skin tone</annotation> + <annotation cp="💇🏼♂">haircut | man | man getting haircut | medium-light skin tone</annotation> + <annotation cp="💇🏼♂" type="tts">man getting haircut: medium-light skin tone</annotation> + <annotation cp="💇🏽♂">haircut | man | man getting haircut | medium skin tone</annotation> + <annotation cp="💇🏽♂" type="tts">man getting haircut: medium skin tone</annotation> + <annotation cp="💇🏾♂">haircut | man | man getting haircut | medium-dark skin tone</annotation> + <annotation cp="💇🏾♂" type="tts">man getting haircut: medium-dark skin tone</annotation> + <annotation cp="💇🏿♂">dark skin tone | haircut | man | man getting haircut</annotation> + <annotation cp="💇🏿♂" type="tts">man getting haircut: dark skin tone</annotation> + <annotation cp="💇🏻♀">haircut | light skin tone | woman | woman getting haircut</annotation> + <annotation cp="💇🏻♀" type="tts">woman getting haircut: light skin tone</annotation> + <annotation cp="💇🏼♀">haircut | medium-light skin tone | woman | woman getting haircut</annotation> + <annotation cp="💇🏼♀" type="tts">woman getting haircut: medium-light skin tone</annotation> + <annotation cp="💇🏽♀">haircut | medium skin tone | woman | woman getting haircut</annotation> + <annotation cp="💇🏽♀" type="tts">woman getting haircut: medium skin tone</annotation> + <annotation cp="💇🏾♀">haircut | medium-dark skin tone | woman | woman getting haircut</annotation> + <annotation cp="💇🏾♀" type="tts">woman getting haircut: medium-dark skin tone</annotation> + <annotation cp="💇🏿♀">dark skin tone | haircut | woman | woman getting haircut</annotation> + <annotation cp="💇🏿♀" type="tts">woman getting haircut: dark skin tone</annotation> + <annotation cp="🚶🏻">hike | light skin tone | person walking | walk | walking</annotation> + <annotation cp="🚶🏻" type="tts">person walking: light skin tone</annotation> + <annotation cp="🚶🏼">hike | medium-light skin tone | person walking | walk | walking</annotation> + <annotation cp="🚶🏼" type="tts">person walking: medium-light skin tone</annotation> + <annotation cp="🚶🏽">hike | medium skin tone | person walking | walk | walking</annotation> + <annotation cp="🚶🏽" type="tts">person walking: medium skin tone</annotation> + <annotation cp="🚶🏾">hike | medium-dark skin tone | person walking | walk | walking</annotation> + <annotation cp="🚶🏾" type="tts">person walking: medium-dark skin tone</annotation> + <annotation cp="🚶🏿">dark skin tone | hike | person walking | walk | walking</annotation> + <annotation cp="🚶🏿" type="tts">person walking: dark skin tone</annotation> + <annotation cp="🚶🏻♂">hike | light skin tone | man | man walking | walk</annotation> + <annotation cp="🚶🏻♂" type="tts">man walking: light skin tone</annotation> + <annotation cp="🚶🏼♂">hike | man | man walking | medium-light skin tone | walk</annotation> + <annotation cp="🚶🏼♂" type="tts">man walking: medium-light skin tone</annotation> + <annotation cp="🚶🏽♂">hike | man | man walking | medium skin tone | walk</annotation> + <annotation cp="🚶🏽♂" type="tts">man walking: medium skin tone</annotation> + <annotation cp="🚶🏾♂">hike | man | man walking | medium-dark skin tone | walk</annotation> + <annotation cp="🚶🏾♂" type="tts">man walking: medium-dark skin tone</annotation> + <annotation cp="🚶🏿♂">dark skin tone | hike | man | man walking | walk</annotation> + <annotation cp="🚶🏿♂" type="tts">man walking: dark skin tone</annotation> + <annotation cp="🚶🏻♀">hike | light skin tone | walk | woman | woman walking</annotation> + <annotation cp="🚶🏻♀" type="tts">woman walking: light skin tone</annotation> + <annotation cp="🚶🏼♀">hike | medium-light skin tone | walk | woman | woman walking</annotation> + <annotation cp="🚶🏼♀" type="tts">woman walking: medium-light skin tone</annotation> + <annotation cp="🚶🏽♀">hike | medium skin tone | walk | woman | woman walking</annotation> + <annotation cp="🚶🏽♀" type="tts">woman walking: medium skin tone</annotation> + <annotation cp="🚶🏾♀">hike | medium-dark skin tone | walk | woman | woman walking</annotation> + <annotation cp="🚶🏾♀" type="tts">woman walking: medium-dark skin tone</annotation> + <annotation cp="🚶🏿♀">dark skin tone | hike | walk | woman | woman walking</annotation> + <annotation cp="🚶🏿♀" type="tts">woman walking: dark skin tone</annotation> + <annotation cp="🧍🏻">light skin tone | person standing | stand | standing</annotation> + <annotation cp="🧍🏻" type="tts">person standing: light skin tone</annotation> + <annotation cp="🧍🏼">medium-light skin tone | person standing | stand | standing</annotation> + <annotation cp="🧍🏼" type="tts">person standing: medium-light skin tone</annotation> + <annotation cp="🧍🏽">medium skin tone | person standing | stand | standing</annotation> + <annotation cp="🧍🏽" type="tts">person standing: medium skin tone</annotation> + <annotation cp="🧍🏾">medium-dark skin tone | person standing | stand | standing</annotation> + <annotation cp="🧍🏾" type="tts">person standing: medium-dark skin tone</annotation> + <annotation cp="🧍🏿">dark skin tone | person standing | stand | standing</annotation> + <annotation cp="🧍🏿" type="tts">person standing: dark skin tone</annotation> + <annotation cp="🧍🏻♂">light skin tone | man | standing</annotation> + <annotation cp="🧍🏻♂" type="tts">man standing: light skin tone</annotation> + <annotation cp="🧍🏼♂">man | medium-light skin tone | standing</annotation> + <annotation cp="🧍🏼♂" type="tts">man standing: medium-light skin tone</annotation> + <annotation cp="🧍🏽♂">man | medium skin tone | standing</annotation> + <annotation cp="🧍🏽♂" type="tts">man standing: medium skin tone</annotation> + <annotation cp="🧍🏾♂">man | medium-dark skin tone | standing</annotation> + <annotation cp="🧍🏾♂" type="tts">man standing: medium-dark skin tone</annotation> + <annotation cp="🧍🏿♂">dark skin tone | man | standing</annotation> + <annotation cp="🧍🏿♂" type="tts">man standing: dark skin tone</annotation> + <annotation cp="🧍🏻♀">light skin tone | standing | woman</annotation> + <annotation cp="🧍🏻♀" type="tts">woman standing: light skin tone</annotation> + <annotation cp="🧍🏼♀">medium-light skin tone | standing | woman</annotation> + <annotation cp="🧍🏼♀" type="tts">woman standing: medium-light skin tone</annotation> + <annotation cp="🧍🏽♀">medium skin tone | standing | woman</annotation> + <annotation cp="🧍🏽♀" type="tts">woman standing: medium skin tone</annotation> + <annotation cp="🧍🏾♀">medium-dark skin tone | standing | woman</annotation> + <annotation cp="🧍🏾♀" type="tts">woman standing: medium-dark skin tone</annotation> + <annotation cp="🧍🏿♀">dark skin tone | standing | woman</annotation> + <annotation cp="🧍🏿♀" type="tts">woman standing: dark skin tone</annotation> + <annotation cp="🧎🏻">kneel | kneeling | light skin tone | person kneeling</annotation> + <annotation cp="🧎🏻" type="tts">person kneeling: light skin tone</annotation> + <annotation cp="🧎🏼">kneel | kneeling | medium-light skin tone | person kneeling</annotation> + <annotation cp="🧎🏼" type="tts">person kneeling: medium-light skin tone</annotation> + <annotation cp="🧎🏽">kneel | kneeling | medium skin tone | person kneeling</annotation> + <annotation cp="🧎🏽" type="tts">person kneeling: medium skin tone</annotation> + <annotation cp="🧎🏾">kneel | kneeling | medium-dark skin tone | person kneeling</annotation> + <annotation cp="🧎🏾" type="tts">person kneeling: medium-dark skin tone</annotation> + <annotation cp="🧎🏿">dark skin tone | kneel | kneeling | person kneeling</annotation> + <annotation cp="🧎🏿" type="tts">person kneeling: dark skin tone</annotation> + <annotation cp="🧎🏻♂">kneeling | light skin tone | man</annotation> + <annotation cp="🧎🏻♂" type="tts">man kneeling: light skin tone</annotation> + <annotation cp="🧎🏼♂">kneeling | man | medium-light skin tone</annotation> + <annotation cp="🧎🏼♂" type="tts">man kneeling: medium-light skin tone</annotation> + <annotation cp="🧎🏽♂">kneeling | man | medium skin tone</annotation> + <annotation cp="🧎🏽♂" type="tts">man kneeling: medium skin tone</annotation> + <annotation cp="🧎🏾♂">kneeling | man | medium-dark skin tone</annotation> + <annotation cp="🧎🏾♂" type="tts">man kneeling: medium-dark skin tone</annotation> + <annotation cp="🧎🏿♂">dark skin tone | kneeling | man</annotation> + <annotation cp="🧎🏿♂" type="tts">man kneeling: dark skin tone</annotation> + <annotation cp="🧎🏻♀">kneeling | light skin tone | woman</annotation> + <annotation cp="🧎🏻♀" type="tts">woman kneeling: light skin tone</annotation> + <annotation cp="🧎🏼♀">kneeling | medium-light skin tone | woman</annotation> + <annotation cp="🧎🏼♀" type="tts">woman kneeling: medium-light skin tone</annotation> + <annotation cp="🧎🏽♀">kneeling | medium skin tone | woman</annotation> + <annotation cp="🧎🏽♀" type="tts">woman kneeling: medium skin tone</annotation> + <annotation cp="🧎🏾♀">kneeling | medium-dark skin tone | woman</annotation> + <annotation cp="🧎🏾♀" type="tts">woman kneeling: medium-dark skin tone</annotation> + <annotation cp="🧎🏿♀">dark skin tone | kneeling | woman</annotation> + <annotation cp="🧎🏿♀" type="tts">woman kneeling: dark skin tone</annotation> + <annotation cp="🧑🏻🦯">accessibility | blind | light skin tone | person with white cane</annotation> + <annotation cp="🧑🏻🦯" type="tts">person with white cane: light skin tone</annotation> + <annotation cp="🧑🏼🦯">accessibility | blind | medium-light skin tone | person with white cane</annotation> + <annotation cp="🧑🏼🦯" type="tts">person with white cane: medium-light skin tone</annotation> + <annotation cp="🧑🏽🦯">accessibility | blind | medium skin tone | person with white cane</annotation> + <annotation cp="🧑🏽🦯" type="tts">person with white cane: medium skin tone</annotation> + <annotation cp="🧑🏾🦯">accessibility | blind | medium-dark skin tone | person with white cane</annotation> + <annotation cp="🧑🏾🦯" type="tts">person with white cane: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🦯">accessibility | blind | dark skin tone | person with white cane</annotation> + <annotation cp="🧑🏿🦯" type="tts">person with white cane: dark skin tone</annotation> + <annotation cp="👨🏻🦯">accessibility | blind | light skin tone | man | man with white cane</annotation> + <annotation cp="👨🏻🦯" type="tts">man with white cane: light skin tone</annotation> + <annotation cp="👨🏼🦯">accessibility | blind | man | man with white cane | medium-light skin tone</annotation> + <annotation cp="👨🏼🦯" type="tts">man with white cane: medium-light skin tone</annotation> + <annotation cp="👨🏽🦯">accessibility | blind | man | man with white cane | medium skin tone</annotation> + <annotation cp="👨🏽🦯" type="tts">man with white cane: medium skin tone</annotation> + <annotation cp="👨🏾🦯">accessibility | blind | man | man with white cane | medium-dark skin tone</annotation> + <annotation cp="👨🏾🦯" type="tts">man with white cane: medium-dark skin tone</annotation> + <annotation cp="👨🏿🦯">accessibility | blind | dark skin tone | man | man with white cane</annotation> + <annotation cp="👨🏿🦯" type="tts">man with white cane: dark skin tone</annotation> + <annotation cp="👩🏻🦯">accessibility | blind | light skin tone | woman | woman with white cane</annotation> + <annotation cp="👩🏻🦯" type="tts">woman with white cane: light skin tone</annotation> + <annotation cp="👩🏼🦯">accessibility | blind | medium-light skin tone | woman | woman with white cane</annotation> + <annotation cp="👩🏼🦯" type="tts">woman with white cane: medium-light skin tone</annotation> + <annotation cp="👩🏽🦯">accessibility | blind | medium skin tone | woman | woman with white cane</annotation> + <annotation cp="👩🏽🦯" type="tts">woman with white cane: medium skin tone</annotation> + <annotation cp="👩🏾🦯">accessibility | blind | medium-dark skin tone | woman | woman with white cane</annotation> + <annotation cp="👩🏾🦯" type="tts">woman with white cane: medium-dark skin tone</annotation> + <annotation cp="👩🏿🦯">accessibility | blind | dark skin tone | woman | woman with white cane</annotation> + <annotation cp="👩🏿🦯" type="tts">woman with white cane: dark skin tone</annotation> + <annotation cp="🧑🏻🦼">accessibility | light skin tone | person in motorized wheelchair | wheelchair</annotation> + <annotation cp="🧑🏻🦼" type="tts">person in motorized wheelchair: light skin tone</annotation> + <annotation cp="🧑🏼🦼">accessibility | medium-light skin tone | person in motorized wheelchair | wheelchair</annotation> + <annotation cp="🧑🏼🦼" type="tts">person in motorized wheelchair: medium-light skin tone</annotation> + <annotation cp="🧑🏽🦼">accessibility | medium skin tone | person in motorized wheelchair | wheelchair</annotation> + <annotation cp="🧑🏽🦼" type="tts">person in motorized wheelchair: medium skin tone</annotation> + <annotation cp="🧑🏾🦼">accessibility | medium-dark skin tone | person in motorized wheelchair | wheelchair</annotation> + <annotation cp="🧑🏾🦼" type="tts">person in motorized wheelchair: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🦼">accessibility | dark skin tone | person in motorized wheelchair | wheelchair</annotation> + <annotation cp="🧑🏿🦼" type="tts">person in motorized wheelchair: dark skin tone</annotation> + <annotation cp="👨🏻🦼">accessibility | light skin tone | man | man in motorized wheelchair | wheelchair</annotation> + <annotation cp="👨🏻🦼" type="tts">man in motorized wheelchair: light skin tone</annotation> + <annotation cp="👨🏼🦼">accessibility | man | man in motorized wheelchair | medium-light skin tone | wheelchair</annotation> + <annotation cp="👨🏼🦼" type="tts">man in motorized wheelchair: medium-light skin tone</annotation> + <annotation cp="👨🏽🦼">accessibility | man | man in motorized wheelchair | medium skin tone | wheelchair</annotation> + <annotation cp="👨🏽🦼" type="tts">man in motorized wheelchair: medium skin tone</annotation> + <annotation cp="👨🏾🦼">accessibility | man | man in motorized wheelchair | medium-dark skin tone | wheelchair</annotation> + <annotation cp="👨🏾🦼" type="tts">man in motorized wheelchair: medium-dark skin tone</annotation> + <annotation cp="👨🏿🦼">accessibility | dark skin tone | man | man in motorized wheelchair | wheelchair</annotation> + <annotation cp="👨🏿🦼" type="tts">man in motorized wheelchair: dark skin tone</annotation> + <annotation cp="👩🏻🦼">accessibility | light skin tone | wheelchair | woman | woman in motorized wheelchair</annotation> + <annotation cp="👩🏻🦼" type="tts">woman in motorized wheelchair: light skin tone</annotation> + <annotation cp="👩🏼🦼">accessibility | medium-light skin tone | wheelchair | woman | woman in motorized wheelchair</annotation> + <annotation cp="👩🏼🦼" type="tts">woman in motorized wheelchair: medium-light skin tone</annotation> + <annotation cp="👩🏽🦼">accessibility | medium skin tone | wheelchair | woman | woman in motorized wheelchair</annotation> + <annotation cp="👩🏽🦼" type="tts">woman in motorized wheelchair: medium skin tone</annotation> + <annotation cp="👩🏾🦼">accessibility | medium-dark skin tone | wheelchair | woman | woman in motorized wheelchair</annotation> + <annotation cp="👩🏾🦼" type="tts">woman in motorized wheelchair: medium-dark skin tone</annotation> + <annotation cp="👩🏿🦼">accessibility | dark skin tone | wheelchair | woman | woman in motorized wheelchair</annotation> + <annotation cp="👩🏿🦼" type="tts">woman in motorized wheelchair: dark skin tone</annotation> + <annotation cp="🧑🏻🦽">accessibility | light skin tone | person in manual wheelchair | wheelchair</annotation> + <annotation cp="🧑🏻🦽" type="tts">person in manual wheelchair: light skin tone</annotation> + <annotation cp="🧑🏼🦽">accessibility | medium-light skin tone | person in manual wheelchair | wheelchair</annotation> + <annotation cp="🧑🏼🦽" type="tts">person in manual wheelchair: medium-light skin tone</annotation> + <annotation cp="🧑🏽🦽">accessibility | medium skin tone | person in manual wheelchair | wheelchair</annotation> + <annotation cp="🧑🏽🦽" type="tts">person in manual wheelchair: medium skin tone</annotation> + <annotation cp="🧑🏾🦽">accessibility | medium-dark skin tone | person in manual wheelchair | wheelchair</annotation> + <annotation cp="🧑🏾🦽" type="tts">person in manual wheelchair: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🦽">accessibility | dark skin tone | person in manual wheelchair | wheelchair</annotation> + <annotation cp="🧑🏿🦽" type="tts">person in manual wheelchair: dark skin tone</annotation> + <annotation cp="👨🏻🦽">accessibility | light skin tone | man | man in manual wheelchair | wheelchair</annotation> + <annotation cp="👨🏻🦽" type="tts">man in manual wheelchair: light skin tone</annotation> + <annotation cp="👨🏼🦽">accessibility | man | man in manual wheelchair | medium-light skin tone | wheelchair</annotation> + <annotation cp="👨🏼🦽" type="tts">man in manual wheelchair: medium-light skin tone</annotation> + <annotation cp="👨🏽🦽">accessibility | man | man in manual wheelchair | medium skin tone | wheelchair</annotation> + <annotation cp="👨🏽🦽" type="tts">man in manual wheelchair: medium skin tone</annotation> + <annotation cp="👨🏾🦽">accessibility | man | man in manual wheelchair | medium-dark skin tone | wheelchair</annotation> + <annotation cp="👨🏾🦽" type="tts">man in manual wheelchair: medium-dark skin tone</annotation> + <annotation cp="👨🏿🦽">accessibility | dark skin tone | man | man in manual wheelchair | wheelchair</annotation> + <annotation cp="👨🏿🦽" type="tts">man in manual wheelchair: dark skin tone</annotation> + <annotation cp="👩🏻🦽">accessibility | light skin tone | wheelchair | woman | woman in manual wheelchair</annotation> + <annotation cp="👩🏻🦽" type="tts">woman in manual wheelchair: light skin tone</annotation> + <annotation cp="👩🏼🦽">accessibility | medium-light skin tone | wheelchair | woman | woman in manual wheelchair</annotation> + <annotation cp="👩🏼🦽" type="tts">woman in manual wheelchair: medium-light skin tone</annotation> + <annotation cp="👩🏽🦽">accessibility | medium skin tone | wheelchair | woman | woman in manual wheelchair</annotation> + <annotation cp="👩🏽🦽" type="tts">woman in manual wheelchair: medium skin tone</annotation> + <annotation cp="👩🏾🦽">accessibility | medium-dark skin tone | wheelchair | woman | woman in manual wheelchair</annotation> + <annotation cp="👩🏾🦽" type="tts">woman in manual wheelchair: medium-dark skin tone</annotation> + <annotation cp="👩🏿🦽">accessibility | dark skin tone | wheelchair | woman | woman in manual wheelchair</annotation> + <annotation cp="👩🏿🦽" type="tts">woman in manual wheelchair: dark skin tone</annotation> + <annotation cp="🏃🏻">light skin tone | marathon | person running | running</annotation> + <annotation cp="🏃🏻" type="tts">person running: light skin tone</annotation> + <annotation cp="🏃🏼">marathon | medium-light skin tone | person running | running</annotation> + <annotation cp="🏃🏼" type="tts">person running: medium-light skin tone</annotation> + <annotation cp="🏃🏽">marathon | medium skin tone | person running | running</annotation> + <annotation cp="🏃🏽" type="tts">person running: medium skin tone</annotation> + <annotation cp="🏃🏾">marathon | medium-dark skin tone | person running | running</annotation> + <annotation cp="🏃🏾" type="tts">person running: medium-dark skin tone</annotation> + <annotation cp="🏃🏿">dark skin tone | marathon | person running | running</annotation> + <annotation cp="🏃🏿" type="tts">person running: dark skin tone</annotation> + <annotation cp="🏃🏻♂">light skin tone | man | marathon | racing | running</annotation> + <annotation cp="🏃🏻♂" type="tts">man running: light skin tone</annotation> + <annotation cp="🏃🏼♂">man | marathon | medium-light skin tone | racing | running</annotation> + <annotation cp="🏃🏼♂" type="tts">man running: medium-light skin tone</annotation> + <annotation cp="🏃🏽♂">man | marathon | medium skin tone | racing | running</annotation> + <annotation cp="🏃🏽♂" type="tts">man running: medium skin tone</annotation> + <annotation cp="🏃🏾♂">man | marathon | medium-dark skin tone | racing | running</annotation> + <annotation cp="🏃🏾♂" type="tts">man running: medium-dark skin tone</annotation> + <annotation cp="🏃🏿♂">dark skin tone | man | marathon | racing | running</annotation> + <annotation cp="🏃🏿♂" type="tts">man running: dark skin tone</annotation> + <annotation cp="🏃🏻♀">light skin tone | marathon | racing | running | woman</annotation> + <annotation cp="🏃🏻♀" type="tts">woman running: light skin tone</annotation> + <annotation cp="🏃🏼♀">marathon | medium-light skin tone | racing | running | woman</annotation> + <annotation cp="🏃🏼♀" type="tts">woman running: medium-light skin tone</annotation> + <annotation cp="🏃🏽♀">marathon | medium skin tone | racing | running | woman</annotation> + <annotation cp="🏃🏽♀" type="tts">woman running: medium skin tone</annotation> + <annotation cp="🏃🏾♀">marathon | medium-dark skin tone | racing | running | woman</annotation> + <annotation cp="🏃🏾♀" type="tts">woman running: medium-dark skin tone</annotation> + <annotation cp="🏃🏿♀">dark skin tone | marathon | racing | running | woman</annotation> + <annotation cp="🏃🏿♀" type="tts">woman running: dark skin tone</annotation> + <annotation cp="💃🏻">dance | dancing | light skin tone | woman</annotation> + <annotation cp="💃🏻" type="tts">woman dancing: light skin tone</annotation> + <annotation cp="💃🏼">dance | dancing | medium-light skin tone | woman</annotation> + <annotation cp="💃🏼" type="tts">woman dancing: medium-light skin tone</annotation> + <annotation cp="💃🏽">dance | dancing | medium skin tone | woman</annotation> + <annotation cp="💃🏽" type="tts">woman dancing: medium skin tone</annotation> + <annotation cp="💃🏾">dance | dancing | medium-dark skin tone | woman</annotation> + <annotation cp="💃🏾" type="tts">woman dancing: medium-dark skin tone</annotation> + <annotation cp="💃🏿">dance | dancing | dark skin tone | woman</annotation> + <annotation cp="💃🏿" type="tts">woman dancing: dark skin tone</annotation> + <annotation cp="🕺🏻">dance | dancing | light skin tone | man</annotation> + <annotation cp="🕺🏻" type="tts">man dancing: light skin tone</annotation> + <annotation cp="🕺🏼">dance | dancing | man | medium-light skin tone</annotation> + <annotation cp="🕺🏼" type="tts">man dancing: medium-light skin tone</annotation> + <annotation cp="🕺🏽">dance | dancing | man | medium skin tone</annotation> + <annotation cp="🕺🏽" type="tts">man dancing: medium skin tone</annotation> + <annotation cp="🕺🏾">dance | dancing | man | medium-dark skin tone</annotation> + <annotation cp="🕺🏾" type="tts">man dancing: medium-dark skin tone</annotation> + <annotation cp="🕺🏿">dance | dancing | dark skin tone | man</annotation> + <annotation cp="🕺🏿" type="tts">man dancing: dark skin tone</annotation> + <annotation cp="🕴🏻">business | light skin tone | person | person in suit levitating | suit</annotation> + <annotation cp="🕴🏻" type="tts">person in suit levitating: light skin tone</annotation> + <annotation cp="🕴🏼">business | medium-light skin tone | person | person in suit levitating | suit</annotation> + <annotation cp="🕴🏼" type="tts">person in suit levitating: medium-light skin tone</annotation> + <annotation cp="🕴🏽">business | medium skin tone | person | person in suit levitating | suit</annotation> + <annotation cp="🕴🏽" type="tts">person in suit levitating: medium skin tone</annotation> + <annotation cp="🕴🏾">business | medium-dark skin tone | person | person in suit levitating | suit</annotation> + <annotation cp="🕴🏾" type="tts">person in suit levitating: medium-dark skin tone</annotation> + <annotation cp="🕴🏿">business | dark skin tone | person | person in suit levitating | suit</annotation> + <annotation cp="🕴🏿" type="tts">person in suit levitating: dark skin tone</annotation> + <annotation cp="🧖🏻">light skin tone | person in steamy room | sauna | steam room</annotation> + <annotation cp="🧖🏻" type="tts">person in steamy room: light skin tone</annotation> + <annotation cp="🧖🏼">medium-light skin tone | person in steamy room | sauna | steam room</annotation> + <annotation cp="🧖🏼" type="tts">person in steamy room: medium-light skin tone</annotation> + <annotation cp="🧖🏽">medium skin tone | person in steamy room | sauna | steam room</annotation> + <annotation cp="🧖🏽" type="tts">person in steamy room: medium skin tone</annotation> + <annotation cp="🧖🏾">medium-dark skin tone | person in steamy room | sauna | steam room</annotation> + <annotation cp="🧖🏾" type="tts">person in steamy room: medium-dark skin tone</annotation> + <annotation cp="🧖🏿">dark skin tone | person in steamy room | sauna | steam room</annotation> + <annotation cp="🧖🏿" type="tts">person in steamy room: dark skin tone</annotation> + <annotation cp="🧖🏻♂">light skin tone | man in steamy room | sauna | steam room</annotation> + <annotation cp="🧖🏻♂" type="tts">man in steamy room: light skin tone</annotation> + <annotation cp="🧖🏼♂">man in steamy room | medium-light skin tone | sauna | steam room</annotation> + <annotation cp="🧖🏼♂" type="tts">man in steamy room: medium-light skin tone</annotation> + <annotation cp="🧖🏽♂">man in steamy room | medium skin tone | sauna | steam room</annotation> + <annotation cp="🧖🏽♂" type="tts">man in steamy room: medium skin tone</annotation> + <annotation cp="🧖🏾♂">man in steamy room | medium-dark skin tone | sauna | steam room</annotation> + <annotation cp="🧖🏾♂" type="tts">man in steamy room: medium-dark skin tone</annotation> + <annotation cp="🧖🏿♂">dark skin tone | man in steamy room | sauna | steam room</annotation> + <annotation cp="🧖🏿♂" type="tts">man in steamy room: dark skin tone</annotation> + <annotation cp="🧖🏻♀">light skin tone | sauna | steam room | woman in steamy room</annotation> + <annotation cp="🧖🏻♀" type="tts">woman in steamy room: light skin tone</annotation> + <annotation cp="🧖🏼♀">medium-light skin tone | sauna | steam room | woman in steamy room</annotation> + <annotation cp="🧖🏼♀" type="tts">woman in steamy room: medium-light skin tone</annotation> + <annotation cp="🧖🏽♀">medium skin tone | sauna | steam room | woman in steamy room</annotation> + <annotation cp="🧖🏽♀" type="tts">woman in steamy room: medium skin tone</annotation> + <annotation cp="🧖🏾♀">medium-dark skin tone | sauna | steam room | woman in steamy room</annotation> + <annotation cp="🧖🏾♀" type="tts">woman in steamy room: medium-dark skin tone</annotation> + <annotation cp="🧖🏿♀">dark skin tone | sauna | steam room | woman in steamy room</annotation> + <annotation cp="🧖🏿♀" type="tts">woman in steamy room: dark skin tone</annotation> + <annotation cp="🧗🏻">climber | light skin tone | person climbing</annotation> + <annotation cp="🧗🏻" type="tts">person climbing: light skin tone</annotation> + <annotation cp="🧗🏼">climber | medium-light skin tone | person climbing</annotation> + <annotation cp="🧗🏼" type="tts">person climbing: medium-light skin tone</annotation> + <annotation cp="🧗🏽">climber | medium skin tone | person climbing</annotation> + <annotation cp="🧗🏽" type="tts">person climbing: medium skin tone</annotation> + <annotation cp="🧗🏾">climber | medium-dark skin tone | person climbing</annotation> + <annotation cp="🧗🏾" type="tts">person climbing: medium-dark skin tone</annotation> + <annotation cp="🧗🏿">climber | dark skin tone | person climbing</annotation> + <annotation cp="🧗🏿" type="tts">person climbing: dark skin tone</annotation> + <annotation cp="🧗🏻♂">climber | light skin tone | man climbing</annotation> + <annotation cp="🧗🏻♂" type="tts">man climbing: light skin tone</annotation> + <annotation cp="🧗🏼♂">climber | man climbing | medium-light skin tone</annotation> + <annotation cp="🧗🏼♂" type="tts">man climbing: medium-light skin tone</annotation> + <annotation cp="🧗🏽♂">climber | man climbing | medium skin tone</annotation> + <annotation cp="🧗🏽♂" type="tts">man climbing: medium skin tone</annotation> + <annotation cp="🧗🏾♂">climber | man climbing | medium-dark skin tone</annotation> + <annotation cp="🧗🏾♂" type="tts">man climbing: medium-dark skin tone</annotation> + <annotation cp="🧗🏿♂">climber | dark skin tone | man climbing</annotation> + <annotation cp="🧗🏿♂" type="tts">man climbing: dark skin tone</annotation> + <annotation cp="🧗🏻♀">climber | light skin tone | woman climbing</annotation> + <annotation cp="🧗🏻♀" type="tts">woman climbing: light skin tone</annotation> + <annotation cp="🧗🏼♀">climber | medium-light skin tone | woman climbing</annotation> + <annotation cp="🧗🏼♀" type="tts">woman climbing: medium-light skin tone</annotation> + <annotation cp="🧗🏽♀">climber | medium skin tone | woman climbing</annotation> + <annotation cp="🧗🏽♀" type="tts">woman climbing: medium skin tone</annotation> + <annotation cp="🧗🏾♀">climber | medium-dark skin tone | woman climbing</annotation> + <annotation cp="🧗🏾♀" type="tts">woman climbing: medium-dark skin tone</annotation> + <annotation cp="🧗🏿♀">climber | dark skin tone | woman climbing</annotation> + <annotation cp="🧗🏿♀" type="tts">woman climbing: dark skin tone</annotation> + <annotation cp="🏇🏻">horse | jockey | light skin tone | racehorse | racing</annotation> + <annotation cp="🏇🏻" type="tts">horse racing: light skin tone</annotation> + <annotation cp="🏇🏼">horse | jockey | medium-light skin tone | racehorse | racing</annotation> + <annotation cp="🏇🏼" type="tts">horse racing: medium-light skin tone</annotation> + <annotation cp="🏇🏽">horse | jockey | medium skin tone | racehorse | racing</annotation> + <annotation cp="🏇🏽" type="tts">horse racing: medium skin tone</annotation> + <annotation cp="🏇🏾">horse | jockey | medium-dark skin tone | racehorse | racing</annotation> + <annotation cp="🏇🏾" type="tts">horse racing: medium-dark skin tone</annotation> + <annotation cp="🏇🏿">dark skin tone | horse | jockey | racehorse | racing</annotation> + <annotation cp="🏇🏿" type="tts">horse racing: dark skin tone</annotation> + <annotation cp="🏂🏻">light skin tone | ski | snow | snowboard | snowboarder</annotation> + <annotation cp="🏂🏻" type="tts">snowboarder: light skin tone</annotation> + <annotation cp="🏂🏼">medium-light skin tone | ski | snow | snowboard | snowboarder</annotation> + <annotation cp="🏂🏼" type="tts">snowboarder: medium-light skin tone</annotation> + <annotation cp="🏂🏽">medium skin tone | ski | snow | snowboard | snowboarder</annotation> + <annotation cp="🏂🏽" type="tts">snowboarder: medium skin tone</annotation> + <annotation cp="🏂🏾">medium-dark skin tone | ski | snow | snowboard | snowboarder</annotation> + <annotation cp="🏂🏾" type="tts">snowboarder: medium-dark skin tone</annotation> + <annotation cp="🏂🏿">dark skin tone | ski | snow | snowboard | snowboarder</annotation> + <annotation cp="🏂🏿" type="tts">snowboarder: dark skin tone</annotation> + <annotation cp="🏌🏻">ball | golf | light skin tone | person golfing</annotation> + <annotation cp="🏌🏻" type="tts">person golfing: light skin tone</annotation> + <annotation cp="🏌🏼">ball | golf | medium-light skin tone | person golfing</annotation> + <annotation cp="🏌🏼" type="tts">person golfing: medium-light skin tone</annotation> + <annotation cp="🏌🏽">ball | golf | medium skin tone | person golfing</annotation> + <annotation cp="🏌🏽" type="tts">person golfing: medium skin tone</annotation> + <annotation cp="🏌🏾">ball | golf | medium-dark skin tone | person golfing</annotation> + <annotation cp="🏌🏾" type="tts">person golfing: medium-dark skin tone</annotation> + <annotation cp="🏌🏿">ball | dark skin tone | golf | person golfing</annotation> + <annotation cp="🏌🏿" type="tts">person golfing: dark skin tone</annotation> + <annotation cp="🏌🏻♂">golf | light skin tone | man | man golfing</annotation> + <annotation cp="🏌🏻♂" type="tts">man golfing: light skin tone</annotation> + <annotation cp="🏌🏼♂">golf | man | man golfing | medium-light skin tone</annotation> + <annotation cp="🏌🏼♂" type="tts">man golfing: medium-light skin tone</annotation> + <annotation cp="🏌🏽♂">golf | man | man golfing | medium skin tone</annotation> + <annotation cp="🏌🏽♂" type="tts">man golfing: medium skin tone</annotation> + <annotation cp="🏌🏾♂">golf | man | man golfing | medium-dark skin tone</annotation> + <annotation cp="🏌🏾♂" type="tts">man golfing: medium-dark skin tone</annotation> + <annotation cp="🏌🏿♂">dark skin tone | golf | man | man golfing</annotation> + <annotation cp="🏌🏿♂" type="tts">man golfing: dark skin tone</annotation> + <annotation cp="🏌🏻♀">golf | light skin tone | woman | woman golfing</annotation> + <annotation cp="🏌🏻♀" type="tts">woman golfing: light skin tone</annotation> + <annotation cp="🏌🏼♀">golf | medium-light skin tone | woman | woman golfing</annotation> + <annotation cp="🏌🏼♀" type="tts">woman golfing: medium-light skin tone</annotation> + <annotation cp="🏌🏽♀">golf | medium skin tone | woman | woman golfing</annotation> + <annotation cp="🏌🏽♀" type="tts">woman golfing: medium skin tone</annotation> + <annotation cp="🏌🏾♀">golf | medium-dark skin tone | woman | woman golfing</annotation> + <annotation cp="🏌🏾♀" type="tts">woman golfing: medium-dark skin tone</annotation> + <annotation cp="🏌🏿♀">dark skin tone | golf | woman | woman golfing</annotation> + <annotation cp="🏌🏿♀" type="tts">woman golfing: dark skin tone</annotation> + <annotation cp="🏄🏻">light skin tone | person surfing | surfing</annotation> + <annotation cp="🏄🏻" type="tts">person surfing: light skin tone</annotation> + <annotation cp="🏄🏼">medium-light skin tone | person surfing | surfing</annotation> + <annotation cp="🏄🏼" type="tts">person surfing: medium-light skin tone</annotation> + <annotation cp="🏄🏽">medium skin tone | person surfing | surfing</annotation> + <annotation cp="🏄🏽" type="tts">person surfing: medium skin tone</annotation> + <annotation cp="🏄🏾">medium-dark skin tone | person surfing | surfing</annotation> + <annotation cp="🏄🏾" type="tts">person surfing: medium-dark skin tone</annotation> + <annotation cp="🏄🏿">dark skin tone | person surfing | surfing</annotation> + <annotation cp="🏄🏿" type="tts">person surfing: dark skin tone</annotation> + <annotation cp="🏄🏻♂">light skin tone | man | surfing</annotation> + <annotation cp="🏄🏻♂" type="tts">man surfing: light skin tone</annotation> + <annotation cp="🏄🏼♂">man | medium-light skin tone | surfing</annotation> + <annotation cp="🏄🏼♂" type="tts">man surfing: medium-light skin tone</annotation> + <annotation cp="🏄🏽♂">man | medium skin tone | surfing</annotation> + <annotation cp="🏄🏽♂" type="tts">man surfing: medium skin tone</annotation> + <annotation cp="🏄🏾♂">man | medium-dark skin tone | surfing</annotation> + <annotation cp="🏄🏾♂" type="tts">man surfing: medium-dark skin tone</annotation> + <annotation cp="🏄🏿♂">dark skin tone | man | surfing</annotation> + <annotation cp="🏄🏿♂" type="tts">man surfing: dark skin tone</annotation> + <annotation cp="🏄🏻♀">light skin tone | surfing | woman</annotation> + <annotation cp="🏄🏻♀" type="tts">woman surfing: light skin tone</annotation> + <annotation cp="🏄🏼♀">medium-light skin tone | surfing | woman</annotation> + <annotation cp="🏄🏼♀" type="tts">woman surfing: medium-light skin tone</annotation> + <annotation cp="🏄🏽♀">medium skin tone | surfing | woman</annotation> + <annotation cp="🏄🏽♀" type="tts">woman surfing: medium skin tone</annotation> + <annotation cp="🏄🏾♀">medium-dark skin tone | surfing | woman</annotation> + <annotation cp="🏄🏾♀" type="tts">woman surfing: medium-dark skin tone</annotation> + <annotation cp="🏄🏿♀">dark skin tone | surfing | woman</annotation> + <annotation cp="🏄🏿♀" type="tts">woman surfing: dark skin tone</annotation> + <annotation cp="🚣🏻">boat | light skin tone | person rowing boat | rowboat</annotation> + <annotation cp="🚣🏻" type="tts">person rowing boat: light skin tone</annotation> + <annotation cp="🚣🏼">boat | medium-light skin tone | person rowing boat | rowboat</annotation> + <annotation cp="🚣🏼" type="tts">person rowing boat: medium-light skin tone</annotation> + <annotation cp="🚣🏽">boat | medium skin tone | person rowing boat | rowboat</annotation> + <annotation cp="🚣🏽" type="tts">person rowing boat: medium skin tone</annotation> + <annotation cp="🚣🏾">boat | medium-dark skin tone | person rowing boat | rowboat</annotation> + <annotation cp="🚣🏾" type="tts">person rowing boat: medium-dark skin tone</annotation> + <annotation cp="🚣🏿">boat | dark skin tone | person rowing boat | rowboat</annotation> + <annotation cp="🚣🏿" type="tts">person rowing boat: dark skin tone</annotation> + <annotation cp="🚣🏻♂">boat | light skin tone | man | man rowing boat | rowboat</annotation> + <annotation cp="🚣🏻♂" type="tts">man rowing boat: light skin tone</annotation> + <annotation cp="🚣🏼♂">boat | man | man rowing boat | medium-light skin tone | rowboat</annotation> + <annotation cp="🚣🏼♂" type="tts">man rowing boat: medium-light skin tone</annotation> + <annotation cp="🚣🏽♂">boat | man | man rowing boat | medium skin tone | rowboat</annotation> + <annotation cp="🚣🏽♂" type="tts">man rowing boat: medium skin tone</annotation> + <annotation cp="🚣🏾♂">boat | man | man rowing boat | medium-dark skin tone | rowboat</annotation> + <annotation cp="🚣🏾♂" type="tts">man rowing boat: medium-dark skin tone</annotation> + <annotation cp="🚣🏿♂">boat | dark skin tone | man | man rowing boat | rowboat</annotation> + <annotation cp="🚣🏿♂" type="tts">man rowing boat: dark skin tone</annotation> + <annotation cp="🚣🏻♀">boat | light skin tone | rowboat | woman | woman rowing boat</annotation> + <annotation cp="🚣🏻♀" type="tts">woman rowing boat: light skin tone</annotation> + <annotation cp="🚣🏼♀">boat | medium-light skin tone | rowboat | woman | woman rowing boat</annotation> + <annotation cp="🚣🏼♀" type="tts">woman rowing boat: medium-light skin tone</annotation> + <annotation cp="🚣🏽♀">boat | medium skin tone | rowboat | woman | woman rowing boat</annotation> + <annotation cp="🚣🏽♀" type="tts">woman rowing boat: medium skin tone</annotation> + <annotation cp="🚣🏾♀">boat | medium-dark skin tone | rowboat | woman | woman rowing boat</annotation> + <annotation cp="🚣🏾♀" type="tts">woman rowing boat: medium-dark skin tone</annotation> + <annotation cp="🚣🏿♀">boat | dark skin tone | rowboat | woman | woman rowing boat</annotation> + <annotation cp="🚣🏿♀" type="tts">woman rowing boat: dark skin tone</annotation> + <annotation cp="🏊🏻">light skin tone | person swimming | swim</annotation> + <annotation cp="🏊🏻" type="tts">person swimming: light skin tone</annotation> + <annotation cp="🏊🏼">medium-light skin tone | person swimming | swim</annotation> + <annotation cp="🏊🏼" type="tts">person swimming: medium-light skin tone</annotation> + <annotation cp="🏊🏽">medium skin tone | person swimming | swim</annotation> + <annotation cp="🏊🏽" type="tts">person swimming: medium skin tone</annotation> + <annotation cp="🏊🏾">medium-dark skin tone | person swimming | swim</annotation> + <annotation cp="🏊🏾" type="tts">person swimming: medium-dark skin tone</annotation> + <annotation cp="🏊🏿">dark skin tone | person swimming | swim</annotation> + <annotation cp="🏊🏿" type="tts">person swimming: dark skin tone</annotation> + <annotation cp="🏊🏻♂">light skin tone | man | man swimming | swim</annotation> + <annotation cp="🏊🏻♂" type="tts">man swimming: light skin tone</annotation> + <annotation cp="🏊🏼♂">man | man swimming | medium-light skin tone | swim</annotation> + <annotation cp="🏊🏼♂" type="tts">man swimming: medium-light skin tone</annotation> + <annotation cp="🏊🏽♂">man | man swimming | medium skin tone | swim</annotation> + <annotation cp="🏊🏽♂" type="tts">man swimming: medium skin tone</annotation> + <annotation cp="🏊🏾♂">man | man swimming | medium-dark skin tone | swim</annotation> + <annotation cp="🏊🏾♂" type="tts">man swimming: medium-dark skin tone</annotation> + <annotation cp="🏊🏿♂">dark skin tone | man | man swimming | swim</annotation> + <annotation cp="🏊🏿♂" type="tts">man swimming: dark skin tone</annotation> + <annotation cp="🏊🏻♀">light skin tone | swim | woman | woman swimming</annotation> + <annotation cp="🏊🏻♀" type="tts">woman swimming: light skin tone</annotation> + <annotation cp="🏊🏼♀">medium-light skin tone | swim | woman | woman swimming</annotation> + <annotation cp="🏊🏼♀" type="tts">woman swimming: medium-light skin tone</annotation> + <annotation cp="🏊🏽♀">medium skin tone | swim | woman | woman swimming</annotation> + <annotation cp="🏊🏽♀" type="tts">woman swimming: medium skin tone</annotation> + <annotation cp="🏊🏾♀">medium-dark skin tone | swim | woman | woman swimming</annotation> + <annotation cp="🏊🏾♀" type="tts">woman swimming: medium-dark skin tone</annotation> + <annotation cp="🏊🏿♀">dark skin tone | swim | woman | woman swimming</annotation> + <annotation cp="🏊🏿♀" type="tts">woman swimming: dark skin tone</annotation> + <annotation cp="⛹🏻">ball | light skin tone | person bouncing ball</annotation> + <annotation cp="⛹🏻" type="tts">person bouncing ball: light skin tone</annotation> + <annotation cp="⛹🏼">ball | medium-light skin tone | person bouncing ball</annotation> + <annotation cp="⛹🏼" type="tts">person bouncing ball: medium-light skin tone</annotation> + <annotation cp="⛹🏽">ball | medium skin tone | person bouncing ball</annotation> + <annotation cp="⛹🏽" type="tts">person bouncing ball: medium skin tone</annotation> + <annotation cp="⛹🏾">ball | medium-dark skin tone | person bouncing ball</annotation> + <annotation cp="⛹🏾" type="tts">person bouncing ball: medium-dark skin tone</annotation> + <annotation cp="⛹🏿">ball | dark skin tone | person bouncing ball</annotation> + <annotation cp="⛹🏿" type="tts">person bouncing ball: dark skin tone</annotation> + <annotation cp="⛹🏻♂">ball | light skin tone | man | man bouncing ball</annotation> + <annotation cp="⛹🏻♂" type="tts">man bouncing ball: light skin tone</annotation> + <annotation cp="⛹🏼♂">ball | man | man bouncing ball | medium-light skin tone</annotation> + <annotation cp="⛹🏼♂" type="tts">man bouncing ball: medium-light skin tone</annotation> + <annotation cp="⛹🏽♂">ball | man | man bouncing ball | medium skin tone</annotation> + <annotation cp="⛹🏽♂" type="tts">man bouncing ball: medium skin tone</annotation> + <annotation cp="⛹🏾♂">ball | man | man bouncing ball | medium-dark skin tone</annotation> + <annotation cp="⛹🏾♂" type="tts">man bouncing ball: medium-dark skin tone</annotation> + <annotation cp="⛹🏿♂">ball | dark skin tone | man | man bouncing ball</annotation> + <annotation cp="⛹🏿♂" type="tts">man bouncing ball: dark skin tone</annotation> + <annotation cp="⛹🏻♀">ball | light skin tone | woman | woman bouncing ball</annotation> + <annotation cp="⛹🏻♀" type="tts">woman bouncing ball: light skin tone</annotation> + <annotation cp="⛹🏼♀">ball | medium-light skin tone | woman | woman bouncing ball</annotation> + <annotation cp="⛹🏼♀" type="tts">woman bouncing ball: medium-light skin tone</annotation> + <annotation cp="⛹🏽♀">ball | medium skin tone | woman | woman bouncing ball</annotation> + <annotation cp="⛹🏽♀" type="tts">woman bouncing ball: medium skin tone</annotation> + <annotation cp="⛹🏾♀">ball | medium-dark skin tone | woman | woman bouncing ball</annotation> + <annotation cp="⛹🏾♀" type="tts">woman bouncing ball: medium-dark skin tone</annotation> + <annotation cp="⛹🏿♀">ball | dark skin tone | woman | woman bouncing ball</annotation> + <annotation cp="⛹🏿♀" type="tts">woman bouncing ball: dark skin tone</annotation> + <annotation cp="🏋🏻">lifter | light skin tone | person lifting weights | weight</annotation> + <annotation cp="🏋🏻" type="tts">person lifting weights: light skin tone</annotation> + <annotation cp="🏋🏼">lifter | medium-light skin tone | person lifting weights | weight</annotation> + <annotation cp="🏋🏼" type="tts">person lifting weights: medium-light skin tone</annotation> + <annotation cp="🏋🏽">lifter | medium skin tone | person lifting weights | weight</annotation> + <annotation cp="🏋🏽" type="tts">person lifting weights: medium skin tone</annotation> + <annotation cp="🏋🏾">lifter | medium-dark skin tone | person lifting weights | weight</annotation> + <annotation cp="🏋🏾" type="tts">person lifting weights: medium-dark skin tone</annotation> + <annotation cp="🏋🏿">dark skin tone | lifter | person lifting weights | weight</annotation> + <annotation cp="🏋🏿" type="tts">person lifting weights: dark skin tone</annotation> + <annotation cp="🏋🏻♂">light skin tone | man | man lifting weights | weight lifter</annotation> + <annotation cp="🏋🏻♂" type="tts">man lifting weights: light skin tone</annotation> + <annotation cp="🏋🏼♂">man | man lifting weights | medium-light skin tone | weight lifter</annotation> + <annotation cp="🏋🏼♂" type="tts">man lifting weights: medium-light skin tone</annotation> + <annotation cp="🏋🏽♂">man | man lifting weights | medium skin tone | weight lifter</annotation> + <annotation cp="🏋🏽♂" type="tts">man lifting weights: medium skin tone</annotation> + <annotation cp="🏋🏾♂">man | man lifting weights | medium-dark skin tone | weight lifter</annotation> + <annotation cp="🏋🏾♂" type="tts">man lifting weights: medium-dark skin tone</annotation> + <annotation cp="🏋🏿♂">dark skin tone | man | man lifting weights | weight lifter</annotation> + <annotation cp="🏋🏿♂" type="tts">man lifting weights: dark skin tone</annotation> + <annotation cp="🏋🏻♀">light skin tone | weight lifter | woman | woman lifting weights</annotation> + <annotation cp="🏋🏻♀" type="tts">woman lifting weights: light skin tone</annotation> + <annotation cp="🏋🏼♀">medium-light skin tone | weight lifter | woman | woman lifting weights</annotation> + <annotation cp="🏋🏼♀" type="tts">woman lifting weights: medium-light skin tone</annotation> + <annotation cp="🏋🏽♀">medium skin tone | weight lifter | woman | woman lifting weights</annotation> + <annotation cp="🏋🏽♀" type="tts">woman lifting weights: medium skin tone</annotation> + <annotation cp="🏋🏾♀">medium-dark skin tone | weight lifter | woman | woman lifting weights</annotation> + <annotation cp="🏋🏾♀" type="tts">woman lifting weights: medium-dark skin tone</annotation> + <annotation cp="🏋🏿♀">dark skin tone | weight lifter | woman | woman lifting weights</annotation> + <annotation cp="🏋🏿♀" type="tts">woman lifting weights: dark skin tone</annotation> + <annotation cp="🚴🏻">bicycle | biking | cyclist | light skin tone | person biking</annotation> + <annotation cp="🚴🏻" type="tts">person biking: light skin tone</annotation> + <annotation cp="🚴🏼">bicycle | biking | cyclist | medium-light skin tone | person biking</annotation> + <annotation cp="🚴🏼" type="tts">person biking: medium-light skin tone</annotation> + <annotation cp="🚴🏽">bicycle | biking | cyclist | medium skin tone | person biking</annotation> + <annotation cp="🚴🏽" type="tts">person biking: medium skin tone</annotation> + <annotation cp="🚴🏾">bicycle | biking | cyclist | medium-dark skin tone | person biking</annotation> + <annotation cp="🚴🏾" type="tts">person biking: medium-dark skin tone</annotation> + <annotation cp="🚴🏿">bicycle | biking | cyclist | dark skin tone | person biking</annotation> + <annotation cp="🚴🏿" type="tts">person biking: dark skin tone</annotation> + <annotation cp="🚴🏻♂">bicycle | biking | cyclist | light skin tone | man</annotation> + <annotation cp="🚴🏻♂" type="tts">man biking: light skin tone</annotation> + <annotation cp="🚴🏼♂">bicycle | biking | cyclist | man | medium-light skin tone</annotation> + <annotation cp="🚴🏼♂" type="tts">man biking: medium-light skin tone</annotation> + <annotation cp="🚴🏽♂">bicycle | biking | cyclist | man | medium skin tone</annotation> + <annotation cp="🚴🏽♂" type="tts">man biking: medium skin tone</annotation> + <annotation cp="🚴🏾♂">bicycle | biking | cyclist | man | medium-dark skin tone</annotation> + <annotation cp="🚴🏾♂" type="tts">man biking: medium-dark skin tone</annotation> + <annotation cp="🚴🏿♂">bicycle | biking | cyclist | dark skin tone | man</annotation> + <annotation cp="🚴🏿♂" type="tts">man biking: dark skin tone</annotation> + <annotation cp="🚴🏻♀">bicycle | biking | cyclist | light skin tone | woman</annotation> + <annotation cp="🚴🏻♀" type="tts">woman biking: light skin tone</annotation> + <annotation cp="🚴🏼♀">bicycle | biking | cyclist | medium-light skin tone | woman</annotation> + <annotation cp="🚴🏼♀" type="tts">woman biking: medium-light skin tone</annotation> + <annotation cp="🚴🏽♀">bicycle | biking | cyclist | medium skin tone | woman</annotation> + <annotation cp="🚴🏽♀" type="tts">woman biking: medium skin tone</annotation> + <annotation cp="🚴🏾♀">bicycle | biking | cyclist | medium-dark skin tone | woman</annotation> + <annotation cp="🚴🏾♀" type="tts">woman biking: medium-dark skin tone</annotation> + <annotation cp="🚴🏿♀">bicycle | biking | cyclist | dark skin tone | woman</annotation> + <annotation cp="🚴🏿♀" type="tts">woman biking: dark skin tone</annotation> + <annotation cp="🚵🏻">bicycle | bicyclist | bike | cyclist | light skin tone | mountain | person mountain biking</annotation> + <annotation cp="🚵🏻" type="tts">person mountain biking: light skin tone</annotation> + <annotation cp="🚵🏼">bicycle | bicyclist | bike | cyclist | medium-light skin tone | mountain | person mountain biking</annotation> + <annotation cp="🚵🏼" type="tts">person mountain biking: medium-light skin tone</annotation> + <annotation cp="🚵🏽">bicycle | bicyclist | bike | cyclist | medium skin tone | mountain | person mountain biking</annotation> + <annotation cp="🚵🏽" type="tts">person mountain biking: medium skin tone</annotation> + <annotation cp="🚵🏾">bicycle | bicyclist | bike | cyclist | medium-dark skin tone | mountain | person mountain biking</annotation> + <annotation cp="🚵🏾" type="tts">person mountain biking: medium-dark skin tone</annotation> + <annotation cp="🚵🏿">bicycle | bicyclist | bike | cyclist | dark skin tone | mountain | person mountain biking</annotation> + <annotation cp="🚵🏿" type="tts">person mountain biking: dark skin tone</annotation> + <annotation cp="🚵🏻♂">bicycle | bike | cyclist | light skin tone | man | man mountain biking | mountain</annotation> + <annotation cp="🚵🏻♂" type="tts">man mountain biking: light skin tone</annotation> + <annotation cp="🚵🏼♂">bicycle | bike | cyclist | man | man mountain biking | medium-light skin tone | mountain</annotation> + <annotation cp="🚵🏼♂" type="tts">man mountain biking: medium-light skin tone</annotation> + <annotation cp="🚵🏽♂">bicycle | bike | cyclist | man | man mountain biking | medium skin tone | mountain</annotation> + <annotation cp="🚵🏽♂" type="tts">man mountain biking: medium skin tone</annotation> + <annotation cp="🚵🏾♂">bicycle | bike | cyclist | man | man mountain biking | medium-dark skin tone | mountain</annotation> + <annotation cp="🚵🏾♂" type="tts">man mountain biking: medium-dark skin tone</annotation> + <annotation cp="🚵🏿♂">bicycle | bike | cyclist | dark skin tone | man | man mountain biking | mountain</annotation> + <annotation cp="🚵🏿♂" type="tts">man mountain biking: dark skin tone</annotation> + <annotation cp="🚵🏻♀">bicycle | bike | biking | cyclist | light skin tone | mountain | woman</annotation> + <annotation cp="🚵🏻♀" type="tts">woman mountain biking: light skin tone</annotation> + <annotation cp="🚵🏼♀">bicycle | bike | biking | cyclist | medium-light skin tone | mountain | woman</annotation> + <annotation cp="🚵🏼♀" type="tts">woman mountain biking: medium-light skin tone</annotation> + <annotation cp="🚵🏽♀">bicycle | bike | biking | cyclist | medium skin tone | mountain | woman</annotation> + <annotation cp="🚵🏽♀" type="tts">woman mountain biking: medium skin tone</annotation> + <annotation cp="🚵🏾♀">bicycle | bike | biking | cyclist | medium-dark skin tone | mountain | woman</annotation> + <annotation cp="🚵🏾♀" type="tts">woman mountain biking: medium-dark skin tone</annotation> + <annotation cp="🚵🏿♀">bicycle | bike | biking | cyclist | dark skin tone | mountain | woman</annotation> + <annotation cp="🚵🏿♀" type="tts">woman mountain biking: dark skin tone</annotation> + <annotation cp="🤸🏻">cartwheel | gymnastics | light skin tone | person cartwheeling</annotation> + <annotation cp="🤸🏻" type="tts">person cartwheeling: light skin tone</annotation> + <annotation cp="🤸🏼">cartwheel | gymnastics | medium-light skin tone | person cartwheeling</annotation> + <annotation cp="🤸🏼" type="tts">person cartwheeling: medium-light skin tone</annotation> + <annotation cp="🤸🏽">cartwheel | gymnastics | medium skin tone | person cartwheeling</annotation> + <annotation cp="🤸🏽" type="tts">person cartwheeling: medium skin tone</annotation> + <annotation cp="🤸🏾">cartwheel | gymnastics | medium-dark skin tone | person cartwheeling</annotation> + <annotation cp="🤸🏾" type="tts">person cartwheeling: medium-dark skin tone</annotation> + <annotation cp="🤸🏿">cartwheel | dark skin tone | gymnastics | person cartwheeling</annotation> + <annotation cp="🤸🏿" type="tts">person cartwheeling: dark skin tone</annotation> + <annotation cp="🤸🏻♂">cartwheel | gymnastics | light skin tone | man | man cartwheeling</annotation> + <annotation cp="🤸🏻♂" type="tts">man cartwheeling: light skin tone</annotation> + <annotation cp="🤸🏼♂">cartwheel | gymnastics | man | man cartwheeling | medium-light skin tone</annotation> + <annotation cp="🤸🏼♂" type="tts">man cartwheeling: medium-light skin tone</annotation> + <annotation cp="🤸🏽♂">cartwheel | gymnastics | man | man cartwheeling | medium skin tone</annotation> + <annotation cp="🤸🏽♂" type="tts">man cartwheeling: medium skin tone</annotation> + <annotation cp="🤸🏾♂">cartwheel | gymnastics | man | man cartwheeling | medium-dark skin tone</annotation> + <annotation cp="🤸🏾♂" type="tts">man cartwheeling: medium-dark skin tone</annotation> + <annotation cp="🤸🏿♂">cartwheel | dark skin tone | gymnastics | man | man cartwheeling</annotation> + <annotation cp="🤸🏿♂" type="tts">man cartwheeling: dark skin tone</annotation> + <annotation cp="🤸🏻♀">cartwheel | gymnastics | light skin tone | woman | woman cartwheeling</annotation> + <annotation cp="🤸🏻♀" type="tts">woman cartwheeling: light skin tone</annotation> + <annotation cp="🤸🏼♀">cartwheel | gymnastics | medium-light skin tone | woman | woman cartwheeling</annotation> + <annotation cp="🤸🏼♀" type="tts">woman cartwheeling: medium-light skin tone</annotation> + <annotation cp="🤸🏽♀">cartwheel | gymnastics | medium skin tone | woman | woman cartwheeling</annotation> + <annotation cp="🤸🏽♀" type="tts">woman cartwheeling: medium skin tone</annotation> + <annotation cp="🤸🏾♀">cartwheel | gymnastics | medium-dark skin tone | woman | woman cartwheeling</annotation> + <annotation cp="🤸🏾♀" type="tts">woman cartwheeling: medium-dark skin tone</annotation> + <annotation cp="🤸🏿♀">cartwheel | dark skin tone | gymnastics | woman | woman cartwheeling</annotation> + <annotation cp="🤸🏿♀" type="tts">woman cartwheeling: dark skin tone</annotation> + <annotation cp="🤽🏻">light skin tone | person playing water polo | polo | water</annotation> + <annotation cp="🤽🏻" type="tts">person playing water polo: light skin tone</annotation> + <annotation cp="🤽🏼">medium-light skin tone | person playing water polo | polo | water</annotation> + <annotation cp="🤽🏼" type="tts">person playing water polo: medium-light skin tone</annotation> + <annotation cp="🤽🏽">medium skin tone | person playing water polo | polo | water</annotation> + <annotation cp="🤽🏽" type="tts">person playing water polo: medium skin tone</annotation> + <annotation cp="🤽🏾">medium-dark skin tone | person playing water polo | polo | water</annotation> + <annotation cp="🤽🏾" type="tts">person playing water polo: medium-dark skin tone</annotation> + <annotation cp="🤽🏿">dark skin tone | person playing water polo | polo | water</annotation> + <annotation cp="🤽🏿" type="tts">person playing water polo: dark skin tone</annotation> + <annotation cp="🤽🏻♂">light skin tone | man | man playing water polo | water polo</annotation> + <annotation cp="🤽🏻♂" type="tts">man playing water polo: light skin tone</annotation> + <annotation cp="🤽🏼♂">man | man playing water polo | medium-light skin tone | water polo</annotation> + <annotation cp="🤽🏼♂" type="tts">man playing water polo: medium-light skin tone</annotation> + <annotation cp="🤽🏽♂">man | man playing water polo | medium skin tone | water polo</annotation> + <annotation cp="🤽🏽♂" type="tts">man playing water polo: medium skin tone</annotation> + <annotation cp="🤽🏾♂">man | man playing water polo | medium-dark skin tone | water polo</annotation> + <annotation cp="🤽🏾♂" type="tts">man playing water polo: medium-dark skin tone</annotation> + <annotation cp="🤽🏿♂">dark skin tone | man | man playing water polo | water polo</annotation> + <annotation cp="🤽🏿♂" type="tts">man playing water polo: dark skin tone</annotation> + <annotation cp="🤽🏻♀">light skin tone | water polo | woman | woman playing water polo</annotation> + <annotation cp="🤽🏻♀" type="tts">woman playing water polo: light skin tone</annotation> + <annotation cp="🤽🏼♀">medium-light skin tone | water polo | woman | woman playing water polo</annotation> + <annotation cp="🤽🏼♀" type="tts">woman playing water polo: medium-light skin tone</annotation> + <annotation cp="🤽🏽♀">medium skin tone | water polo | woman | woman playing water polo</annotation> + <annotation cp="🤽🏽♀" type="tts">woman playing water polo: medium skin tone</annotation> + <annotation cp="🤽🏾♀">medium-dark skin tone | water polo | woman | woman playing water polo</annotation> + <annotation cp="🤽🏾♀" type="tts">woman playing water polo: medium-dark skin tone</annotation> + <annotation cp="🤽🏿♀">dark skin tone | water polo | woman | woman playing water polo</annotation> + <annotation cp="🤽🏿♀" type="tts">woman playing water polo: dark skin tone</annotation> + <annotation cp="🤾🏻">ball | handball | light skin tone | person playing handball</annotation> + <annotation cp="🤾🏻" type="tts">person playing handball: light skin tone</annotation> + <annotation cp="🤾🏼">ball | handball | medium-light skin tone | person playing handball</annotation> + <annotation cp="🤾🏼" type="tts">person playing handball: medium-light skin tone</annotation> + <annotation cp="🤾🏽">ball | handball | medium skin tone | person playing handball</annotation> + <annotation cp="🤾🏽" type="tts">person playing handball: medium skin tone</annotation> + <annotation cp="🤾🏾">ball | handball | medium-dark skin tone | person playing handball</annotation> + <annotation cp="🤾🏾" type="tts">person playing handball: medium-dark skin tone</annotation> + <annotation cp="🤾🏿">ball | dark skin tone | handball | person playing handball</annotation> + <annotation cp="🤾🏿" type="tts">person playing handball: dark skin tone</annotation> + <annotation cp="🤾🏻♂">handball | light skin tone | man | man playing handball</annotation> + <annotation cp="🤾🏻♂" type="tts">man playing handball: light skin tone</annotation> + <annotation cp="🤾🏼♂">handball | man | man playing handball | medium-light skin tone</annotation> + <annotation cp="🤾🏼♂" type="tts">man playing handball: medium-light skin tone</annotation> + <annotation cp="🤾🏽♂">handball | man | man playing handball | medium skin tone</annotation> + <annotation cp="🤾🏽♂" type="tts">man playing handball: medium skin tone</annotation> + <annotation cp="🤾🏾♂">handball | man | man playing handball | medium-dark skin tone</annotation> + <annotation cp="🤾🏾♂" type="tts">man playing handball: medium-dark skin tone</annotation> + <annotation cp="🤾🏿♂">dark skin tone | handball | man | man playing handball</annotation> + <annotation cp="🤾🏿♂" type="tts">man playing handball: dark skin tone</annotation> + <annotation cp="🤾🏻♀">handball | light skin tone | woman | woman playing handball</annotation> + <annotation cp="🤾🏻♀" type="tts">woman playing handball: light skin tone</annotation> + <annotation cp="🤾🏼♀">handball | medium-light skin tone | woman | woman playing handball</annotation> + <annotation cp="🤾🏼♀" type="tts">woman playing handball: medium-light skin tone</annotation> + <annotation cp="🤾🏽♀">handball | medium skin tone | woman | woman playing handball</annotation> + <annotation cp="🤾🏽♀" type="tts">woman playing handball: medium skin tone</annotation> + <annotation cp="🤾🏾♀">handball | medium-dark skin tone | woman | woman playing handball</annotation> + <annotation cp="🤾🏾♀" type="tts">woman playing handball: medium-dark skin tone</annotation> + <annotation cp="🤾🏿♀">dark skin tone | handball | woman | woman playing handball</annotation> + <annotation cp="🤾🏿♀" type="tts">woman playing handball: dark skin tone</annotation> + <annotation cp="🤹🏻">balance | juggle | light skin tone | multitask | person juggling | skill</annotation> + <annotation cp="🤹🏻" type="tts">person juggling: light skin tone</annotation> + <annotation cp="🤹🏼">balance | juggle | medium-light skin tone | multitask | person juggling | skill</annotation> + <annotation cp="🤹🏼" type="tts">person juggling: medium-light skin tone</annotation> + <annotation cp="🤹🏽">balance | juggle | medium skin tone | multitask | person juggling | skill</annotation> + <annotation cp="🤹🏽" type="tts">person juggling: medium skin tone</annotation> + <annotation cp="🤹🏾">balance | juggle | medium-dark skin tone | multitask | person juggling | skill</annotation> + <annotation cp="🤹🏾" type="tts">person juggling: medium-dark skin tone</annotation> + <annotation cp="🤹🏿">balance | dark skin tone | juggle | multitask | person juggling | skill</annotation> + <annotation cp="🤹🏿" type="tts">person juggling: dark skin tone</annotation> + <annotation cp="🤹🏻♂">juggling | light skin tone | man | multitask</annotation> + <annotation cp="🤹🏻♂" type="tts">man juggling: light skin tone</annotation> + <annotation cp="🤹🏼♂">juggling | man | medium-light skin tone | multitask</annotation> + <annotation cp="🤹🏼♂" type="tts">man juggling: medium-light skin tone</annotation> + <annotation cp="🤹🏽♂">juggling | man | medium skin tone | multitask</annotation> + <annotation cp="🤹🏽♂" type="tts">man juggling: medium skin tone</annotation> + <annotation cp="🤹🏾♂">juggling | man | medium-dark skin tone | multitask</annotation> + <annotation cp="🤹🏾♂" type="tts">man juggling: medium-dark skin tone</annotation> + <annotation cp="🤹🏿♂">dark skin tone | juggling | man | multitask</annotation> + <annotation cp="🤹🏿♂" type="tts">man juggling: dark skin tone</annotation> + <annotation cp="🤹🏻♀">juggling | light skin tone | multitask | woman</annotation> + <annotation cp="🤹🏻♀" type="tts">woman juggling: light skin tone</annotation> + <annotation cp="🤹🏼♀">juggling | medium-light skin tone | multitask | woman</annotation> + <annotation cp="🤹🏼♀" type="tts">woman juggling: medium-light skin tone</annotation> + <annotation cp="🤹🏽♀">juggling | medium skin tone | multitask | woman</annotation> + <annotation cp="🤹🏽♀" type="tts">woman juggling: medium skin tone</annotation> + <annotation cp="🤹🏾♀">juggling | medium-dark skin tone | multitask | woman</annotation> + <annotation cp="🤹🏾♀" type="tts">woman juggling: medium-dark skin tone</annotation> + <annotation cp="🤹🏿♀">dark skin tone | juggling | multitask | woman</annotation> + <annotation cp="🤹🏿♀" type="tts">woman juggling: dark skin tone</annotation> + <annotation cp="🧘🏻">light skin tone | meditation | person in lotus position | yoga</annotation> + <annotation cp="🧘🏻" type="tts">person in lotus position: light skin tone</annotation> + <annotation cp="🧘🏼">meditation | medium-light skin tone | person in lotus position | yoga</annotation> + <annotation cp="🧘🏼" type="tts">person in lotus position: medium-light skin tone</annotation> + <annotation cp="🧘🏽">meditation | medium skin tone | person in lotus position | yoga</annotation> + <annotation cp="🧘🏽" type="tts">person in lotus position: medium skin tone</annotation> + <annotation cp="🧘🏾">meditation | medium-dark skin tone | person in lotus position | yoga</annotation> + <annotation cp="🧘🏾" type="tts">person in lotus position: medium-dark skin tone</annotation> + <annotation cp="🧘🏿">dark skin tone | meditation | person in lotus position | yoga</annotation> + <annotation cp="🧘🏿" type="tts">person in lotus position: dark skin tone</annotation> + <annotation cp="🧘🏻♂">light skin tone | man in lotus position | meditation | yoga</annotation> + <annotation cp="🧘🏻♂" type="tts">man in lotus position: light skin tone</annotation> + <annotation cp="🧘🏼♂">man in lotus position | meditation | medium-light skin tone | yoga</annotation> + <annotation cp="🧘🏼♂" type="tts">man in lotus position: medium-light skin tone</annotation> + <annotation cp="🧘🏽♂">man in lotus position | meditation | medium skin tone | yoga</annotation> + <annotation cp="🧘🏽♂" type="tts">man in lotus position: medium skin tone</annotation> + <annotation cp="🧘🏾♂">man in lotus position | meditation | medium-dark skin tone | yoga</annotation> + <annotation cp="🧘🏾♂" type="tts">man in lotus position: medium-dark skin tone</annotation> + <annotation cp="🧘🏿♂">dark skin tone | man in lotus position | meditation | yoga</annotation> + <annotation cp="🧘🏿♂" type="tts">man in lotus position: dark skin tone</annotation> + <annotation cp="🧘🏻♀">light skin tone | meditation | woman in lotus position | yoga</annotation> + <annotation cp="🧘🏻♀" type="tts">woman in lotus position: light skin tone</annotation> + <annotation cp="🧘🏼♀">meditation | medium-light skin tone | woman in lotus position | yoga</annotation> + <annotation cp="🧘🏼♀" type="tts">woman in lotus position: medium-light skin tone</annotation> + <annotation cp="🧘🏽♀">meditation | medium skin tone | woman in lotus position | yoga</annotation> + <annotation cp="🧘🏽♀" type="tts">woman in lotus position: medium skin tone</annotation> + <annotation cp="🧘🏾♀">meditation | medium-dark skin tone | woman in lotus position | yoga</annotation> + <annotation cp="🧘🏾♀" type="tts">woman in lotus position: medium-dark skin tone</annotation> + <annotation cp="🧘🏿♀">dark skin tone | meditation | woman in lotus position | yoga</annotation> + <annotation cp="🧘🏿♀" type="tts">woman in lotus position: dark skin tone</annotation> + <annotation cp="🛀🏻">bath | bathtub | light skin tone | person taking bath</annotation> + <annotation cp="🛀🏻" type="tts">person taking bath: light skin tone</annotation> + <annotation cp="🛀🏼">bath | bathtub | medium-light skin tone | person taking bath</annotation> + <annotation cp="🛀🏼" type="tts">person taking bath: medium-light skin tone</annotation> + <annotation cp="🛀🏽">bath | bathtub | medium skin tone | person taking bath</annotation> + <annotation cp="🛀🏽" type="tts">person taking bath: medium skin tone</annotation> + <annotation cp="🛀🏾">bath | bathtub | medium-dark skin tone | person taking bath</annotation> + <annotation cp="🛀🏾" type="tts">person taking bath: medium-dark skin tone</annotation> + <annotation cp="🛀🏿">bath | bathtub | dark skin tone | person taking bath</annotation> + <annotation cp="🛀🏿" type="tts">person taking bath: dark skin tone</annotation> + <annotation cp="🛌🏻">hotel | light skin tone | person in bed | sleep</annotation> + <annotation cp="🛌🏻" type="tts">person in bed: light skin tone</annotation> + <annotation cp="🛌🏼">hotel | medium-light skin tone | person in bed | sleep</annotation> + <annotation cp="🛌🏼" type="tts">person in bed: medium-light skin tone</annotation> + <annotation cp="🛌🏽">hotel | medium skin tone | person in bed | sleep</annotation> + <annotation cp="🛌🏽" type="tts">person in bed: medium skin tone</annotation> + <annotation cp="🛌🏾">hotel | medium-dark skin tone | person in bed | sleep</annotation> + <annotation cp="🛌🏾" type="tts">person in bed: medium-dark skin tone</annotation> + <annotation cp="🛌🏿">dark skin tone | hotel | person in bed | sleep</annotation> + <annotation cp="🛌🏿" type="tts">person in bed: dark skin tone</annotation> + <annotation cp="🧑🏻🤝🧑🏻">couple | hand | hold | holding hands | light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏻🤝🧑🏻" type="tts">people holding hands: light skin tone</annotation> + <annotation cp="🧑🏻🤝🧑🏼">couple | hand | hold | holding hands | light skin tone | medium-light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏻🤝🧑🏼" type="tts">people holding hands: light skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏻🤝🧑🏽">couple | hand | hold | holding hands | light skin tone | medium skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏻🤝🧑🏽" type="tts">people holding hands: light skin tone, medium skin tone</annotation> + <annotation cp="🧑🏻🤝🧑🏾">couple | hand | hold | holding hands | light skin tone | medium-dark skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏻🤝🧑🏾" type="tts">people holding hands: light skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏻🤝🧑🏿">couple | dark skin tone | hand | hold | holding hands | light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏻🤝🧑🏿" type="tts">people holding hands: light skin tone, dark skin tone</annotation> + <annotation cp="🧑🏼🤝🧑🏻">couple | hand | hold | holding hands | light skin tone | medium-light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏼🤝🧑🏻" type="tts">people holding hands: medium-light skin tone, light skin tone</annotation> + <annotation cp="🧑🏼🤝🧑🏼">couple | hand | hold | holding hands | medium-light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏼🤝🧑🏼" type="tts">people holding hands: medium-light skin tone</annotation> + <annotation cp="🧑🏼🤝🧑🏽">couple | hand | hold | holding hands | medium skin tone | medium-light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏼🤝🧑🏽" type="tts">people holding hands: medium-light skin tone, medium skin tone</annotation> + <annotation cp="🧑🏼🤝🧑🏾">couple | hand | hold | holding hands | medium-dark skin tone | medium-light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏼🤝🧑🏾" type="tts">people holding hands: medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏼🤝🧑🏿">couple | dark skin tone | hand | hold | holding hands | medium-light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏼🤝🧑🏿" type="tts">people holding hands: medium-light skin tone, dark skin tone</annotation> + <annotation cp="🧑🏽🤝🧑🏻">couple | hand | hold | holding hands | light skin tone | medium skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏽🤝🧑🏻" type="tts">people holding hands: medium skin tone, light skin tone</annotation> + <annotation cp="🧑🏽🤝🧑🏼">couple | hand | hold | holding hands | medium skin tone | medium-light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏽🤝🧑🏼" type="tts">people holding hands: medium skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏽🤝🧑🏽">couple | hand | hold | holding hands | medium skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏽🤝🧑🏽" type="tts">people holding hands: medium skin tone</annotation> + <annotation cp="🧑🏽🤝🧑🏾">couple | hand | hold | holding hands | medium skin tone | medium-dark skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏽🤝🧑🏾" type="tts">people holding hands: medium skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏽🤝🧑🏿">couple | dark skin tone | hand | hold | holding hands | medium skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏽🤝🧑🏿" type="tts">people holding hands: medium skin tone, dark skin tone</annotation> + <annotation cp="🧑🏾🤝🧑🏻">couple | hand | hold | holding hands | light skin tone | medium-dark skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏾🤝🧑🏻" type="tts">people holding hands: medium-dark skin tone, light skin tone</annotation> + <annotation cp="🧑🏾🤝🧑🏼">couple | hand | hold | holding hands | medium-dark skin tone | medium-light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏾🤝🧑🏼" type="tts">people holding hands: medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏾🤝🧑🏽">couple | hand | hold | holding hands | medium skin tone | medium-dark skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏾🤝🧑🏽" type="tts">people holding hands: medium-dark skin tone, medium skin tone</annotation> + <annotation cp="🧑🏾🤝🧑🏾">couple | hand | hold | holding hands | medium-dark skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏾🤝🧑🏾" type="tts">people holding hands: medium-dark skin tone</annotation> + <annotation cp="🧑🏾🤝🧑🏿">couple | dark skin tone | hand | hold | holding hands | medium-dark skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏾🤝🧑🏿" type="tts">people holding hands: medium-dark skin tone, dark skin tone</annotation> + <annotation cp="🧑🏿🤝🧑🏻">couple | dark skin tone | hand | hold | holding hands | light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏿🤝🧑🏻" type="tts">people holding hands: dark skin tone, light skin tone</annotation> + <annotation cp="🧑🏿🤝🧑🏼">couple | dark skin tone | hand | hold | holding hands | medium-light skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏿🤝🧑🏼" type="tts">people holding hands: dark skin tone, medium-light skin tone</annotation> + <annotation cp="🧑🏿🤝🧑🏽">couple | dark skin tone | hand | hold | holding hands | medium skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏿🤝🧑🏽" type="tts">people holding hands: dark skin tone, medium skin tone</annotation> + <annotation cp="🧑🏿🤝🧑🏾">couple | dark skin tone | hand | hold | holding hands | medium-dark skin tone | people holding hands | person</annotation> + <annotation cp="🧑🏿🤝🧑🏾" type="tts">people holding hands: dark skin tone, medium-dark skin tone</annotation> + <annotation cp="🧑🏿🤝🧑🏿">couple | dark skin tone | hand | hold | holding hands | people holding hands | person</annotation> + <annotation cp="🧑🏿🤝🧑🏿" type="tts">people holding hands: dark skin tone</annotation> + <annotation cp="👭🏻">couple | hand | holding hands | light skin tone | women | women holding hands</annotation> + <annotation cp="👭🏻" type="tts">women holding hands: light skin tone</annotation> + <annotation cp="👩🏻🤝👩🏼">couple | hand | holding hands | light skin tone | medium-light skin tone | women | women holding hands</annotation> + <annotation cp="👩🏻🤝👩🏼" type="tts">women holding hands: light skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏻🤝👩🏽">couple | hand | holding hands | light skin tone | medium skin tone | women | women holding hands</annotation> + <annotation cp="👩🏻🤝👩🏽" type="tts">women holding hands: light skin tone, medium skin tone</annotation> + <annotation cp="👩🏻🤝👩🏾">couple | hand | holding hands | light skin tone | medium-dark skin tone | women | women holding hands</annotation> + <annotation cp="👩🏻🤝👩🏾" type="tts">women holding hands: light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏻🤝👩🏿">couple | dark skin tone | hand | holding hands | light skin tone | women | women holding hands</annotation> + <annotation cp="👩🏻🤝👩🏿" type="tts">women holding hands: light skin tone, dark skin tone</annotation> + <annotation cp="👩🏼🤝👩🏻">couple | hand | holding hands | light skin tone | medium-light skin tone | women | women holding hands</annotation> + <annotation cp="👩🏼🤝👩🏻" type="tts">women holding hands: medium-light skin tone, light skin tone</annotation> + <annotation cp="👭🏼">couple | hand | holding hands | medium-light skin tone | women | women holding hands</annotation> + <annotation cp="👭🏼" type="tts">women holding hands: medium-light skin tone</annotation> + <annotation cp="👩🏼🤝👩🏽">couple | hand | holding hands | medium skin tone | medium-light skin tone | women | women holding hands</annotation> + <annotation cp="👩🏼🤝👩🏽" type="tts">women holding hands: medium-light skin tone, medium skin tone</annotation> + <annotation cp="👩🏼🤝👩🏾">couple | hand | holding hands | medium-dark skin tone | medium-light skin tone | women | women holding hands</annotation> + <annotation cp="👩🏼🤝👩🏾" type="tts">women holding hands: medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏼🤝👩🏿">couple | dark skin tone | hand | holding hands | medium-light skin tone | women | women holding hands</annotation> + <annotation cp="👩🏼🤝👩🏿" type="tts">women holding hands: medium-light skin tone, dark skin tone</annotation> + <annotation cp="👩🏽🤝👩🏻">couple | hand | holding hands | light skin tone | medium skin tone | women | women holding hands</annotation> + <annotation cp="👩🏽🤝👩🏻" type="tts">women holding hands: medium skin tone, light skin tone</annotation> + <annotation cp="👩🏽🤝👩🏼">couple | hand | holding hands | medium skin tone | medium-light skin tone | women | women holding hands</annotation> + <annotation cp="👩🏽🤝👩🏼" type="tts">women holding hands: medium skin tone, medium-light skin tone</annotation> + <annotation cp="👭🏽">couple | hand | holding hands | medium skin tone | women | women holding hands</annotation> + <annotation cp="👭🏽" type="tts">women holding hands: medium skin tone</annotation> + <annotation cp="👩🏽🤝👩🏾">couple | hand | holding hands | medium skin tone | medium-dark skin tone | women | women holding hands</annotation> + <annotation cp="👩🏽🤝👩🏾" type="tts">women holding hands: medium skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏽🤝👩🏿">couple | dark skin tone | hand | holding hands | medium skin tone | women | women holding hands</annotation> + <annotation cp="👩🏽🤝👩🏿" type="tts">women holding hands: medium skin tone, dark skin tone</annotation> + <annotation cp="👩🏾🤝👩🏻">couple | hand | holding hands | light skin tone | medium-dark skin tone | women | women holding hands</annotation> + <annotation cp="👩🏾🤝👩🏻" type="tts">women holding hands: medium-dark skin tone, light skin tone</annotation> + <annotation cp="👩🏾🤝👩🏼">couple | hand | holding hands | medium-dark skin tone | medium-light skin tone | women | women holding hands</annotation> + <annotation cp="👩🏾🤝👩🏼" type="tts">women holding hands: medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏾🤝👩🏽">couple | hand | holding hands | medium skin tone | medium-dark skin tone | women | women holding hands</annotation> + <annotation cp="👩🏾🤝👩🏽" type="tts">women holding hands: medium-dark skin tone, medium skin tone</annotation> + <annotation cp="👭🏾">couple | hand | holding hands | medium-dark skin tone | women | women holding hands</annotation> + <annotation cp="👭🏾" type="tts">women holding hands: medium-dark skin tone</annotation> + <annotation cp="👩🏾🤝👩🏿">couple | dark skin tone | hand | holding hands | medium-dark skin tone | women | women holding hands</annotation> + <annotation cp="👩🏾🤝👩🏿" type="tts">women holding hands: medium-dark skin tone, dark skin tone</annotation> + <annotation cp="👩🏿🤝👩🏻">couple | dark skin tone | hand | holding hands | light skin tone | women | women holding hands</annotation> + <annotation cp="👩🏿🤝👩🏻" type="tts">women holding hands: dark skin tone, light skin tone</annotation> + <annotation cp="👩🏿🤝👩🏼">couple | dark skin tone | hand | holding hands | medium-light skin tone | women | women holding hands</annotation> + <annotation cp="👩🏿🤝👩🏼" type="tts">women holding hands: dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏿🤝👩🏽">couple | dark skin tone | hand | holding hands | medium skin tone | women | women holding hands</annotation> + <annotation cp="👩🏿🤝👩🏽" type="tts">women holding hands: dark skin tone, medium skin tone</annotation> + <annotation cp="👩🏿🤝👩🏾">couple | dark skin tone | hand | holding hands | medium-dark skin tone | women | women holding hands</annotation> + <annotation cp="👩🏿🤝👩🏾" type="tts">women holding hands: dark skin tone, medium-dark skin tone</annotation> + <annotation cp="👭🏿">couple | dark skin tone | hand | holding hands | women | women holding hands</annotation> + <annotation cp="👭🏿" type="tts">women holding hands: dark skin tone</annotation> + <annotation cp="👫🏻">couple | hand | hold | holding hands | light skin tone | man | woman | woman and man holding hands</annotation> + <annotation cp="👫🏻" type="tts">woman and man holding hands: light skin tone</annotation> + <annotation cp="👩🏻🤝👨🏼">couple | hand | hold | holding hands | light skin tone | man | medium-light skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏻🤝👨🏼" type="tts">woman and man holding hands: light skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏻🤝👨🏽">couple | hand | hold | holding hands | light skin tone | man | medium skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏻🤝👨🏽" type="tts">woman and man holding hands: light skin tone, medium skin tone</annotation> + <annotation cp="👩🏻🤝👨🏾">couple | hand | hold | holding hands | light skin tone | man | medium-dark skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏻🤝👨🏾" type="tts">woman and man holding hands: light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏻🤝👨🏿">couple | dark skin tone | hand | hold | holding hands | light skin tone | man | woman | woman and man holding hands</annotation> + <annotation cp="👩🏻🤝👨🏿" type="tts">woman and man holding hands: light skin tone, dark skin tone</annotation> + <annotation cp="👩🏼🤝👨🏻">couple | hand | hold | holding hands | light skin tone | man | medium-light skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏼🤝👨🏻" type="tts">woman and man holding hands: medium-light skin tone, light skin tone</annotation> + <annotation cp="👫🏼">couple | hand | hold | holding hands | man | medium-light skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👫🏼" type="tts">woman and man holding hands: medium-light skin tone</annotation> + <annotation cp="👩🏼🤝👨🏽">couple | hand | hold | holding hands | man | medium skin tone | medium-light skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏼🤝👨🏽" type="tts">woman and man holding hands: medium-light skin tone, medium skin tone</annotation> + <annotation cp="👩🏼🤝👨🏾">couple | hand | hold | holding hands | man | medium-dark skin tone | medium-light skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏼🤝👨🏾" type="tts">woman and man holding hands: medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏼🤝👨🏿">couple | dark skin tone | hand | hold | holding hands | man | medium-light skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏼🤝👨🏿" type="tts">woman and man holding hands: medium-light skin tone, dark skin tone</annotation> + <annotation cp="👩🏽🤝👨🏻">couple | hand | hold | holding hands | light skin tone | man | medium skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏽🤝👨🏻" type="tts">woman and man holding hands: medium skin tone, light skin tone</annotation> + <annotation cp="👩🏽🤝👨🏼">couple | hand | hold | holding hands | man | medium skin tone | medium-light skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏽🤝👨🏼" type="tts">woman and man holding hands: medium skin tone, medium-light skin tone</annotation> + <annotation cp="👫🏽">couple | hand | hold | holding hands | man | medium skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👫🏽" type="tts">woman and man holding hands: medium skin tone</annotation> + <annotation cp="👩🏽🤝👨🏾">couple | hand | hold | holding hands | man | medium skin tone | medium-dark skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏽🤝👨🏾" type="tts">woman and man holding hands: medium skin tone, medium-dark skin tone</annotation> + <annotation cp="👩🏽🤝👨🏿">couple | dark skin tone | hand | hold | holding hands | man | medium skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏽🤝👨🏿" type="tts">woman and man holding hands: medium skin tone, dark skin tone</annotation> + <annotation cp="👩🏾🤝👨🏻">couple | hand | hold | holding hands | light skin tone | man | medium-dark skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏾🤝👨🏻" type="tts">woman and man holding hands: medium-dark skin tone, light skin tone</annotation> + <annotation cp="👩🏾🤝👨🏼">couple | hand | hold | holding hands | man | medium-dark skin tone | medium-light skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏾🤝👨🏼" type="tts">woman and man holding hands: medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏾🤝👨🏽">couple | hand | hold | holding hands | man | medium skin tone | medium-dark skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏾🤝👨🏽" type="tts">woman and man holding hands: medium-dark skin tone, medium skin tone</annotation> + <annotation cp="👫🏾">couple | hand | hold | holding hands | man | medium-dark skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👫🏾" type="tts">woman and man holding hands: medium-dark skin tone</annotation> + <annotation cp="👩🏾🤝👨🏿">couple | dark skin tone | hand | hold | holding hands | man | medium-dark skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏾🤝👨🏿" type="tts">woman and man holding hands: medium-dark skin tone, dark skin tone</annotation> + <annotation cp="👩🏿🤝👨🏻">couple | dark skin tone | hand | hold | holding hands | light skin tone | man | woman | woman and man holding hands</annotation> + <annotation cp="👩🏿🤝👨🏻" type="tts">woman and man holding hands: dark skin tone, light skin tone</annotation> + <annotation cp="👩🏿🤝👨🏼">couple | dark skin tone | hand | hold | holding hands | man | medium-light skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏿🤝👨🏼" type="tts">woman and man holding hands: dark skin tone, medium-light skin tone</annotation> + <annotation cp="👩🏿🤝👨🏽">couple | dark skin tone | hand | hold | holding hands | man | medium skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏿🤝👨🏽" type="tts">woman and man holding hands: dark skin tone, medium skin tone</annotation> + <annotation cp="👩🏿🤝👨🏾">couple | dark skin tone | hand | hold | holding hands | man | medium-dark skin tone | woman | woman and man holding hands</annotation> + <annotation cp="👩🏿🤝👨🏾" type="tts">woman and man holding hands: dark skin tone, medium-dark skin tone</annotation> + <annotation cp="👫🏿">couple | dark skin tone | hand | hold | holding hands | man | woman | woman and man holding hands</annotation> + <annotation cp="👫🏿" type="tts">woman and man holding hands: dark skin tone</annotation> + <annotation cp="👬🏻">couple | Gemini | holding hands | light skin tone | man | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👬🏻" type="tts">men holding hands: light skin tone</annotation> + <annotation cp="👨🏻🤝👨🏼">couple | Gemini | holding hands | light skin tone | man | medium-light skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏻🤝👨🏼" type="tts">men holding hands: light skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏻🤝👨🏽">couple | Gemini | holding hands | light skin tone | man | medium skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏻🤝👨🏽" type="tts">men holding hands: light skin tone, medium skin tone</annotation> + <annotation cp="👨🏻🤝👨🏾">couple | Gemini | holding hands | light skin tone | man | medium-dark skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏻🤝👨🏾" type="tts">men holding hands: light skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏻🤝👨🏿">couple | dark skin tone | Gemini | holding hands | light skin tone | man | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏻🤝👨🏿" type="tts">men holding hands: light skin tone, dark skin tone</annotation> + <annotation cp="👨🏼🤝👨🏻">couple | Gemini | holding hands | light skin tone | man | medium-light skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏼🤝👨🏻" type="tts">men holding hands: medium-light skin tone, light skin tone</annotation> + <annotation cp="👬🏼">couple | Gemini | holding hands | man | medium-light skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👬🏼" type="tts">men holding hands: medium-light skin tone</annotation> + <annotation cp="👨🏼🤝👨🏽">couple | Gemini | holding hands | man | medium skin tone | medium-light skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏼🤝👨🏽" type="tts">men holding hands: medium-light skin tone, medium skin tone</annotation> + <annotation cp="👨🏼🤝👨🏾">couple | Gemini | holding hands | man | medium-dark skin tone | medium-light skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏼🤝👨🏾" type="tts">men holding hands: medium-light skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏼🤝👨🏿">couple | dark skin tone | Gemini | holding hands | man | medium-light skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏼🤝👨🏿" type="tts">men holding hands: medium-light skin tone, dark skin tone</annotation> + <annotation cp="👨🏽🤝👨🏻">couple | Gemini | holding hands | light skin tone | man | medium skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏽🤝👨🏻" type="tts">men holding hands: medium skin tone, light skin tone</annotation> + <annotation cp="👨🏽🤝👨🏼">couple | Gemini | holding hands | man | medium skin tone | medium-light skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏽🤝👨🏼" type="tts">men holding hands: medium skin tone, medium-light skin tone</annotation> + <annotation cp="👬🏽">couple | Gemini | holding hands | man | medium skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👬🏽" type="tts">men holding hands: medium skin tone</annotation> + <annotation cp="👨🏽🤝👨🏾">couple | Gemini | holding hands | man | medium skin tone | medium-dark skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏽🤝👨🏾" type="tts">men holding hands: medium skin tone, medium-dark skin tone</annotation> + <annotation cp="👨🏽🤝👨🏿">couple | dark skin tone | Gemini | holding hands | man | medium skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏽🤝👨🏿" type="tts">men holding hands: medium skin tone, dark skin tone</annotation> + <annotation cp="👨🏾🤝👨🏻">couple | Gemini | holding hands | light skin tone | man | medium-dark skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏾🤝👨🏻" type="tts">men holding hands: medium-dark skin tone, light skin tone</annotation> + <annotation cp="👨🏾🤝👨🏼">couple | Gemini | holding hands | man | medium-dark skin tone | medium-light skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏾🤝👨🏼" type="tts">men holding hands: medium-dark skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏾🤝👨🏽">couple | Gemini | holding hands | man | medium skin tone | medium-dark skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏾🤝👨🏽" type="tts">men holding hands: medium-dark skin tone, medium skin tone</annotation> + <annotation cp="👬🏾">couple | Gemini | holding hands | man | medium-dark skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👬🏾" type="tts">men holding hands: medium-dark skin tone</annotation> + <annotation cp="👨🏾🤝👨🏿">couple | dark skin tone | Gemini | holding hands | man | medium-dark skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏾🤝👨🏿" type="tts">men holding hands: medium-dark skin tone, dark skin tone</annotation> + <annotation cp="👨🏿🤝👨🏻">couple | dark skin tone | Gemini | holding hands | light skin tone | man | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏿🤝👨🏻" type="tts">men holding hands: dark skin tone, light skin tone</annotation> + <annotation cp="👨🏿🤝👨🏼">couple | dark skin tone | Gemini | holding hands | man | medium-light skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏿🤝👨🏼" type="tts">men holding hands: dark skin tone, medium-light skin tone</annotation> + <annotation cp="👨🏿🤝👨🏽">couple | dark skin tone | Gemini | holding hands | man | medium skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏿🤝👨🏽" type="tts">men holding hands: dark skin tone, medium skin tone</annotation> + <annotation cp="👨🏿🤝👨🏾">couple | dark skin tone | Gemini | holding hands | man | medium-dark skin tone | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👨🏿🤝👨🏾" type="tts">men holding hands: dark skin tone, medium-dark skin tone</annotation> + <annotation cp="👬🏿">couple | dark skin tone | Gemini | holding hands | man | men | men holding hands | twins | zodiac</annotation> + <annotation cp="👬🏿" type="tts">men holding hands: dark skin tone</annotation> + <annotation cp="💏🏻">couple | kiss | light skin tone</annotation> + <annotation cp="💏🏻" type="tts">kiss: light skin tone</annotation> + <annotation cp="💏🏼">couple | kiss | medium-light skin tone</annotation> + <annotation cp="💏🏼" type="tts">kiss: medium-light skin tone</annotation> + <annotation cp="💏🏽">couple | kiss | medium skin tone</annotation> + <annotation cp="💏🏽" type="tts">kiss: medium skin tone</annotation> + <annotation cp="💏🏾">couple | kiss | medium-dark skin tone</annotation> + <annotation cp="💏🏾" type="tts">kiss: medium-dark skin tone</annotation> + <annotation cp="💏🏿">couple | dark skin tone | kiss</annotation> + <annotation cp="💏🏿" type="tts">kiss: dark skin tone</annotation> + <annotation cp="👩❤💋👨">couple | kiss | man | woman</annotation> + <annotation cp="👩❤💋👨" type="tts">kiss: woman, man</annotation> + <annotation cp="👨❤💋👨">couple | kiss | man</annotation> + <annotation cp="👨❤💋👨" type="tts">kiss: man, man</annotation> + <annotation cp="👩❤💋👩">couple | kiss | woman</annotation> + <annotation cp="👩❤💋👩" type="tts">kiss: woman, woman</annotation> + <annotation cp="💑🏻">couple | couple with heart | light skin tone | love</annotation> + <annotation cp="💑🏻" type="tts">couple with heart: light skin tone</annotation> + <annotation cp="💑🏼">couple | couple with heart | love | medium-light skin tone</annotation> + <annotation cp="💑🏼" type="tts">couple with heart: medium-light skin tone</annotation> + <annotation cp="💑🏽">couple | couple with heart | love | medium skin tone</annotation> + <annotation cp="💑🏽" type="tts">couple with heart: medium skin tone</annotation> + <annotation cp="💑🏾">couple | couple with heart | love | medium-dark skin tone</annotation> + <annotation cp="💑🏾" type="tts">couple with heart: medium-dark skin tone</annotation> + <annotation cp="💑🏿">couple | couple with heart | dark skin tone | love</annotation> + <annotation cp="💑🏿" type="tts">couple with heart: dark skin tone</annotation> + <annotation cp="👩❤👨">couple | couple with heart | love | man | woman</annotation> + <annotation cp="👩❤👨" type="tts">couple with heart: woman, man</annotation> + <annotation cp="👨❤👨">couple | couple with heart | love | man</annotation> + <annotation cp="👨❤👨" type="tts">couple with heart: man, man</annotation> + <annotation cp="👩❤👩">couple | couple with heart | love | woman</annotation> + <annotation cp="👩❤👩" type="tts">couple with heart: woman, woman</annotation> + <annotation cp="👨👩👦">boy | family | man | woman</annotation> + <annotation cp="👨👩👦" type="tts">family: man, woman, boy</annotation> + <annotation cp="👨👩👧">family | girl | man | woman</annotation> + <annotation cp="👨👩👧" type="tts">family: man, woman, girl</annotation> + <annotation cp="👨👩👧👦">boy | family | girl | man | woman</annotation> + <annotation cp="👨👩👧👦" type="tts">family: man, woman, girl, boy</annotation> + <annotation cp="👨👩👦👦">boy | family | man | woman</annotation> + <annotation cp="👨👩👦👦" type="tts">family: man, woman, boy, boy</annotation> + <annotation cp="👨👩👧👧">family | girl | man | woman</annotation> + <annotation cp="👨👩👧👧" type="tts">family: man, woman, girl, girl</annotation> + <annotation cp="👨👨👦">boy | family | man</annotation> + <annotation cp="👨👨👦" type="tts">family: man, man, boy</annotation> + <annotation cp="👨👨👧">family | girl | man</annotation> + <annotation cp="👨👨👧" type="tts">family: man, man, girl</annotation> + <annotation cp="👨👨👧👦">boy | family | girl | man</annotation> + <annotation cp="👨👨👧👦" type="tts">family: man, man, girl, boy</annotation> + <annotation cp="👨👨👦👦">boy | family | man</annotation> + <annotation cp="👨👨👦👦" type="tts">family: man, man, boy, boy</annotation> + <annotation cp="👨👨👧👧">family | girl | man</annotation> + <annotation cp="👨👨👧👧" type="tts">family: man, man, girl, girl</annotation> + <annotation cp="👩👩👦">boy | family | woman</annotation> + <annotation cp="👩👩👦" type="tts">family: woman, woman, boy</annotation> + <annotation cp="👩👩👧">family | girl | woman</annotation> + <annotation cp="👩👩👧" type="tts">family: woman, woman, girl</annotation> + <annotation cp="👩👩👧👦">boy | family | girl | woman</annotation> + <annotation cp="👩👩👧👦" type="tts">family: woman, woman, girl, boy</annotation> + <annotation cp="👩👩👦👦">boy | family | woman</annotation> + <annotation cp="👩👩👦👦" type="tts">family: woman, woman, boy, boy</annotation> + <annotation cp="👩👩👧👧">family | girl | woman</annotation> + <annotation cp="👩👩👧👧" type="tts">family: woman, woman, girl, girl</annotation> + <annotation cp="👨👦">boy | family | man</annotation> + <annotation cp="👨👦" type="tts">family: man, boy</annotation> + <annotation cp="👨👦👦">boy | family | man</annotation> + <annotation cp="👨👦👦" type="tts">family: man, boy, boy</annotation> + <annotation cp="👨👧">family | girl | man</annotation> + <annotation cp="👨👧" type="tts">family: man, girl</annotation> + <annotation cp="👨👧👦">boy | family | girl | man</annotation> + <annotation cp="👨👧👦" type="tts">family: man, girl, boy</annotation> + <annotation cp="👨👧👧">family | girl | man</annotation> + <annotation cp="👨👧👧" type="tts">family: man, girl, girl</annotation> + <annotation cp="👩👦">boy | family | woman</annotation> + <annotation cp="👩👦" type="tts">family: woman, boy</annotation> + <annotation cp="👩👦👦">boy | family | woman</annotation> + <annotation cp="👩👦👦" type="tts">family: woman, boy, boy</annotation> + <annotation cp="👩👧">family | girl | woman</annotation> + <annotation cp="👩👧" type="tts">family: woman, girl</annotation> + <annotation cp="👩👧👦">boy | family | girl | woman</annotation> + <annotation cp="👩👧👦" type="tts">family: woman, girl, boy</annotation> + <annotation cp="👩👧👧">family | girl | woman</annotation> + <annotation cp="👩👧👧" type="tts">family: woman, girl, girl</annotation> + <annotation cp="#⃣">keycap</annotation> + <annotation cp="#⃣" type="tts">keycap: #</annotation> + <annotation cp="*⃣">keycap</annotation> + <annotation cp="*⃣" type="tts">keycap: *</annotation> + <annotation cp="🔟">keycap</annotation> + <annotation cp="🔟" type="tts">keycap: 10</annotation> + <annotation cp="🇦🇨">flag</annotation> + <annotation cp="🇦🇨" type="tts">flag: Ascension Island</annotation> + <annotation cp="🇦🇩">flag</annotation> + <annotation cp="🇦🇩" type="tts">flag: Andorra</annotation> + <annotation cp="🇦🇪">flag</annotation> + <annotation cp="🇦🇪" type="tts">flag: United Arab Emirates</annotation> + <annotation cp="🇦🇫">flag</annotation> + <annotation cp="🇦🇫" type="tts">flag: Afghanistan</annotation> + <annotation cp="🇦🇬">flag</annotation> + <annotation cp="🇦🇬" type="tts">flag: Antigua & Barbuda</annotation> + <annotation cp="🇦🇮">flag</annotation> + <annotation cp="🇦🇮" type="tts">flag: Anguilla</annotation> + <annotation cp="🇦🇱">flag</annotation> + <annotation cp="🇦🇱" type="tts">flag: Albania</annotation> + <annotation cp="🇦🇲">flag</annotation> + <annotation cp="🇦🇲" type="tts">flag: Armenia</annotation> + <annotation cp="🇦🇴">flag</annotation> + <annotation cp="🇦🇴" type="tts">flag: Angola</annotation> + <annotation cp="🇦🇶">flag</annotation> + <annotation cp="🇦🇶" type="tts">flag: Antarctica</annotation> + <annotation cp="🇦🇷">flag</annotation> + <annotation cp="🇦🇷" type="tts">flag: Argentina</annotation> + <annotation cp="🇦🇸">flag</annotation> + <annotation cp="🇦🇸" type="tts">flag: American Samoa</annotation> + <annotation cp="🇦🇹">flag</annotation> + <annotation cp="🇦🇹" type="tts">flag: Austria</annotation> + <annotation cp="🇦🇺">flag</annotation> + <annotation cp="🇦🇺" type="tts">flag: Australia</annotation> + <annotation cp="🇦🇼">flag</annotation> + <annotation cp="🇦🇼" type="tts">flag: Aruba</annotation> + <annotation cp="🇦🇽">flag</annotation> + <annotation cp="🇦🇽" type="tts">flag: Åland Islands</annotation> + <annotation cp="🇦🇿">flag</annotation> + <annotation cp="🇦🇿" type="tts">flag: Azerbaijan</annotation> + <annotation cp="🇧🇦">flag</annotation> + <annotation cp="🇧🇦" type="tts">flag: Bosnia & Herzegovina</annotation> + <annotation cp="🇧🇧">flag</annotation> + <annotation cp="🇧🇧" type="tts">flag: Barbados</annotation> + <annotation cp="🇧🇩">flag</annotation> + <annotation cp="🇧🇩" type="tts">flag: Bangladesh</annotation> + <annotation cp="🇧🇪">flag</annotation> + <annotation cp="🇧🇪" type="tts">flag: Belgium</annotation> + <annotation cp="🇧🇫">flag</annotation> + <annotation cp="🇧🇫" type="tts">flag: Burkina Faso</annotation> + <annotation cp="🇧🇬">flag</annotation> + <annotation cp="🇧🇬" type="tts">flag: Bulgaria</annotation> + <annotation cp="🇧🇭">flag</annotation> + <annotation cp="🇧🇭" type="tts">flag: Bahrain</annotation> + <annotation cp="🇧🇮">flag</annotation> + <annotation cp="🇧🇮" type="tts">flag: Burundi</annotation> + <annotation cp="🇧🇯">flag</annotation> + <annotation cp="🇧🇯" type="tts">flag: Benin</annotation> + <annotation cp="🇧🇱">flag</annotation> + <annotation cp="🇧🇱" type="tts">flag: St. Barthélemy</annotation> + <annotation cp="🇧🇲">flag</annotation> + <annotation cp="🇧🇲" type="tts">flag: Bermuda</annotation> + <annotation cp="🇧🇳">flag</annotation> + <annotation cp="🇧🇳" type="tts">flag: Brunei</annotation> + <annotation cp="🇧🇴">flag</annotation> + <annotation cp="🇧🇴" type="tts">flag: Bolivia</annotation> + <annotation cp="🇧🇶">flag</annotation> + <annotation cp="🇧🇶" type="tts">flag: Caribbean Netherlands</annotation> + <annotation cp="🇧🇷">flag</annotation> + <annotation cp="🇧🇷" type="tts">flag: Brazil</annotation> + <annotation cp="🇧🇸">flag</annotation> + <annotation cp="🇧🇸" type="tts">flag: Bahamas</annotation> + <annotation cp="🇧🇹">flag</annotation> + <annotation cp="🇧🇹" type="tts">flag: Bhutan</annotation> + <annotation cp="🇧🇻">flag</annotation> + <annotation cp="🇧🇻" type="tts">flag: Bouvet Island</annotation> + <annotation cp="🇧🇼">flag</annotation> + <annotation cp="🇧🇼" type="tts">flag: Botswana</annotation> + <annotation cp="🇧🇾">flag</annotation> + <annotation cp="🇧🇾" type="tts">flag: Belarus</annotation> + <annotation cp="🇧🇿">flag</annotation> + <annotation cp="🇧🇿" type="tts">flag: Belize</annotation> + <annotation cp="🇨🇦">flag</annotation> + <annotation cp="🇨🇦" type="tts">flag: Canada</annotation> + <annotation cp="🇨🇨">flag</annotation> + <annotation cp="🇨🇨" type="tts">flag: Cocos (Keeling) Islands</annotation> + <annotation cp="🇨🇩">flag</annotation> + <annotation cp="🇨🇩" type="tts">flag: Congo - Kinshasa</annotation> + <annotation cp="🇨🇫">flag</annotation> + <annotation cp="🇨🇫" type="tts">flag: Central African Republic</annotation> + <annotation cp="🇨🇬">flag</annotation> + <annotation cp="🇨🇬" type="tts">flag: Congo - Brazzaville</annotation> + <annotation cp="🇨🇭">flag</annotation> + <annotation cp="🇨🇭" type="tts">flag: Switzerland</annotation> + <annotation cp="🇨🇮">flag</annotation> + <annotation cp="🇨🇮" type="tts">flag: Côte d’Ivoire</annotation> + <annotation cp="🇨🇰">flag</annotation> + <annotation cp="🇨🇰" type="tts">flag: Cook Islands</annotation> + <annotation cp="🇨🇱">flag</annotation> + <annotation cp="🇨🇱" type="tts">flag: Chile</annotation> + <annotation cp="🇨🇲">flag</annotation> + <annotation cp="🇨🇲" type="tts">flag: Cameroon</annotation> + <annotation cp="🇨🇳">flag</annotation> + <annotation cp="🇨🇳" type="tts">flag: China</annotation> + <annotation cp="🇨🇴">flag</annotation> + <annotation cp="🇨🇴" type="tts">flag: Colombia</annotation> + <annotation cp="🇨🇵">flag</annotation> + <annotation cp="🇨🇵" type="tts">flag: Clipperton Island</annotation> + <annotation cp="🇨🇷">flag</annotation> + <annotation cp="🇨🇷" type="tts">flag: Costa Rica</annotation> + <annotation cp="🇨🇺">flag</annotation> + <annotation cp="🇨🇺" type="tts">flag: Cuba</annotation> + <annotation cp="🇨🇻">flag</annotation> + <annotation cp="🇨🇻" type="tts">flag: Cape Verde</annotation> + <annotation cp="🇨🇼">flag</annotation> + <annotation cp="🇨🇼" type="tts">flag: Curaçao</annotation> + <annotation cp="🇨🇽">flag</annotation> + <annotation cp="🇨🇽" type="tts">flag: Christmas Island</annotation> + <annotation cp="🇨🇾">flag</annotation> + <annotation cp="🇨🇾" type="tts">flag: Cyprus</annotation> + <annotation cp="🇨🇿">flag</annotation> + <annotation cp="🇨🇿" type="tts">flag: Czechia</annotation> + <annotation cp="🇩🇪">flag</annotation> + <annotation cp="🇩🇪" type="tts">flag: Germany</annotation> + <annotation cp="🇩🇬">flag</annotation> + <annotation cp="🇩🇬" type="tts">flag: Diego Garcia</annotation> + <annotation cp="🇩🇯">flag</annotation> + <annotation cp="🇩🇯" type="tts">flag: Djibouti</annotation> + <annotation cp="🇩🇰">flag</annotation> + <annotation cp="🇩🇰" type="tts">flag: Denmark</annotation> + <annotation cp="🇩🇲">flag</annotation> + <annotation cp="🇩🇲" type="tts">flag: Dominica</annotation> + <annotation cp="🇩🇴">flag</annotation> + <annotation cp="🇩🇴" type="tts">flag: Dominican Republic</annotation> + <annotation cp="🇩🇿">flag</annotation> + <annotation cp="🇩🇿" type="tts">flag: Algeria</annotation> + <annotation cp="🇪🇦">flag</annotation> + <annotation cp="🇪🇦" type="tts">flag: Ceuta & Melilla</annotation> + <annotation cp="🇪🇨">flag</annotation> + <annotation cp="🇪🇨" type="tts">flag: Ecuador</annotation> + <annotation cp="🇪🇪">flag</annotation> + <annotation cp="🇪🇪" type="tts">flag: Estonia</annotation> + <annotation cp="🇪🇬">flag</annotation> + <annotation cp="🇪🇬" type="tts">flag: Egypt</annotation> + <annotation cp="🇪🇭">flag</annotation> + <annotation cp="🇪🇭" type="tts">flag: Western Sahara</annotation> + <annotation cp="🇪🇷">flag</annotation> + <annotation cp="🇪🇷" type="tts">flag: Eritrea</annotation> + <annotation cp="🇪🇸">flag</annotation> + <annotation cp="🇪🇸" type="tts">flag: Spain</annotation> + <annotation cp="🇪🇹">flag</annotation> + <annotation cp="🇪🇹" type="tts">flag: Ethiopia</annotation> + <annotation cp="🇪🇺">flag</annotation> + <annotation cp="🇪🇺" type="tts">flag: European Union</annotation> + <annotation cp="🇫🇮">flag</annotation> + <annotation cp="🇫🇮" type="tts">flag: Finland</annotation> + <annotation cp="🇫🇯">flag</annotation> + <annotation cp="🇫🇯" type="tts">flag: Fiji</annotation> + <annotation cp="🇫🇰">flag</annotation> + <annotation cp="🇫🇰" type="tts">flag: Falkland Islands</annotation> + <annotation cp="🇫🇲">flag</annotation> + <annotation cp="🇫🇲" type="tts">flag: Micronesia</annotation> + <annotation cp="🇫🇴">flag</annotation> + <annotation cp="🇫🇴" type="tts">flag: Faroe Islands</annotation> + <annotation cp="🇫🇷">flag</annotation> + <annotation cp="🇫🇷" type="tts">flag: France</annotation> + <annotation cp="🇬🇦">flag</annotation> + <annotation cp="🇬🇦" type="tts">flag: Gabon</annotation> + <annotation cp="🇬🇧">flag</annotation> + <annotation cp="🇬🇧" type="tts">flag: United Kingdom</annotation> + <annotation cp="🇬🇩">flag</annotation> + <annotation cp="🇬🇩" type="tts">flag: Grenada</annotation> + <annotation cp="🇬🇪">flag</annotation> + <annotation cp="🇬🇪" type="tts">flag: Georgia</annotation> + <annotation cp="🇬🇫">flag</annotation> + <annotation cp="🇬🇫" type="tts">flag: French Guiana</annotation> + <annotation cp="🇬🇬">flag</annotation> + <annotation cp="🇬🇬" type="tts">flag: Guernsey</annotation> + <annotation cp="🇬🇭">flag</annotation> + <annotation cp="🇬🇭" type="tts">flag: Ghana</annotation> + <annotation cp="🇬🇮">flag</annotation> + <annotation cp="🇬🇮" type="tts">flag: Gibraltar</annotation> + <annotation cp="🇬🇱">flag</annotation> + <annotation cp="🇬🇱" type="tts">flag: Greenland</annotation> + <annotation cp="🇬🇲">flag</annotation> + <annotation cp="🇬🇲" type="tts">flag: Gambia</annotation> + <annotation cp="🇬🇳">flag</annotation> + <annotation cp="🇬🇳" type="tts">flag: Guinea</annotation> + <annotation cp="🇬🇵">flag</annotation> + <annotation cp="🇬🇵" type="tts">flag: Guadeloupe</annotation> + <annotation cp="🇬🇶">flag</annotation> + <annotation cp="🇬🇶" type="tts">flag: Equatorial Guinea</annotation> + <annotation cp="🇬🇷">flag</annotation> + <annotation cp="🇬🇷" type="tts">flag: Greece</annotation> + <annotation cp="🇬🇸">flag</annotation> + <annotation cp="🇬🇸" type="tts">flag: South Georgia & South Sandwich Islands</annotation> + <annotation cp="🇬🇹">flag</annotation> + <annotation cp="🇬🇹" type="tts">flag: Guatemala</annotation> + <annotation cp="🇬🇺">flag</annotation> + <annotation cp="🇬🇺" type="tts">flag: Guam</annotation> + <annotation cp="🇬🇼">flag</annotation> + <annotation cp="🇬🇼" type="tts">flag: Guinea-Bissau</annotation> + <annotation cp="🇬🇾">flag</annotation> + <annotation cp="🇬🇾" type="tts">flag: Guyana</annotation> + <annotation cp="🇭🇰">flag</annotation> + <annotation cp="🇭🇰" type="tts">flag: Hong Kong SAR China</annotation> + <annotation cp="🇭🇲">flag</annotation> + <annotation cp="🇭🇲" type="tts">flag: Heard & McDonald Islands</annotation> + <annotation cp="🇭🇳">flag</annotation> + <annotation cp="🇭🇳" type="tts">flag: Honduras</annotation> + <annotation cp="🇭🇷">flag</annotation> + <annotation cp="🇭🇷" type="tts">flag: Croatia</annotation> + <annotation cp="🇭🇹">flag</annotation> + <annotation cp="🇭🇹" type="tts">flag: Haiti</annotation> + <annotation cp="🇭🇺">flag</annotation> + <annotation cp="🇭🇺" type="tts">flag: Hungary</annotation> + <annotation cp="🇮🇨">flag</annotation> + <annotation cp="🇮🇨" type="tts">flag: Canary Islands</annotation> + <annotation cp="🇮🇩">flag</annotation> + <annotation cp="🇮🇩" type="tts">flag: Indonesia</annotation> + <annotation cp="🇮🇪">flag</annotation> + <annotation cp="🇮🇪" type="tts">flag: Ireland</annotation> + <annotation cp="🇮🇱">flag</annotation> + <annotation cp="🇮🇱" type="tts">flag: Israel</annotation> + <annotation cp="🇮🇲">flag</annotation> + <annotation cp="🇮🇲" type="tts">flag: Isle of Man</annotation> + <annotation cp="🇮🇳">flag</annotation> + <annotation cp="🇮🇳" type="tts">flag: India</annotation> + <annotation cp="🇮🇴">flag</annotation> + <annotation cp="🇮🇴" type="tts">flag: British Indian Ocean Territory</annotation> + <annotation cp="🇮🇶">flag</annotation> + <annotation cp="🇮🇶" type="tts">flag: Iraq</annotation> + <annotation cp="🇮🇷">flag</annotation> + <annotation cp="🇮🇷" type="tts">flag: Iran</annotation> + <annotation cp="🇮🇸">flag</annotation> + <annotation cp="🇮🇸" type="tts">flag: Iceland</annotation> + <annotation cp="🇮🇹">flag</annotation> + <annotation cp="🇮🇹" type="tts">flag: Italy</annotation> + <annotation cp="🇯🇪">flag</annotation> + <annotation cp="🇯🇪" type="tts">flag: Jersey</annotation> + <annotation cp="🇯🇲">flag</annotation> + <annotation cp="🇯🇲" type="tts">flag: Jamaica</annotation> + <annotation cp="🇯🇴">flag</annotation> + <annotation cp="🇯🇴" type="tts">flag: Jordan</annotation> + <annotation cp="🇯🇵">flag</annotation> + <annotation cp="🇯🇵" type="tts">flag: Japan</annotation> + <annotation cp="🇰🇪">flag</annotation> + <annotation cp="🇰🇪" type="tts">flag: Kenya</annotation> + <annotation cp="🇰🇬">flag</annotation> + <annotation cp="🇰🇬" type="tts">flag: Kyrgyzstan</annotation> + <annotation cp="🇰🇭">flag</annotation> + <annotation cp="🇰🇭" type="tts">flag: Cambodia</annotation> + <annotation cp="🇰🇮">flag</annotation> + <annotation cp="🇰🇮" type="tts">flag: Kiribati</annotation> + <annotation cp="🇰🇲">flag</annotation> + <annotation cp="🇰🇲" type="tts">flag: Comoros</annotation> + <annotation cp="🇰🇳">flag</annotation> + <annotation cp="🇰🇳" type="tts">flag: St. Kitts & Nevis</annotation> + <annotation cp="🇰🇵">flag</annotation> + <annotation cp="🇰🇵" type="tts">flag: North Korea</annotation> + <annotation cp="🇰🇷">flag</annotation> + <annotation cp="🇰🇷" type="tts">flag: South Korea</annotation> + <annotation cp="🇰🇼">flag</annotation> + <annotation cp="🇰🇼" type="tts">flag: Kuwait</annotation> + <annotation cp="🇰🇾">flag</annotation> + <annotation cp="🇰🇾" type="tts">flag: Cayman Islands</annotation> + <annotation cp="🇰🇿">flag</annotation> + <annotation cp="🇰🇿" type="tts">flag: Kazakhstan</annotation> + <annotation cp="🇱🇦">flag</annotation> + <annotation cp="🇱🇦" type="tts">flag: Laos</annotation> + <annotation cp="🇱🇧">flag</annotation> + <annotation cp="🇱🇧" type="tts">flag: Lebanon</annotation> + <annotation cp="🇱🇨">flag</annotation> + <annotation cp="🇱🇨" type="tts">flag: St. Lucia</annotation> + <annotation cp="🇱🇮">flag</annotation> + <annotation cp="🇱🇮" type="tts">flag: Liechtenstein</annotation> + <annotation cp="🇱🇰">flag</annotation> + <annotation cp="🇱🇰" type="tts">flag: Sri Lanka</annotation> + <annotation cp="🇱🇷">flag</annotation> + <annotation cp="🇱🇷" type="tts">flag: Liberia</annotation> + <annotation cp="🇱🇸">flag</annotation> + <annotation cp="🇱🇸" type="tts">flag: Lesotho</annotation> + <annotation cp="🇱🇹">flag</annotation> + <annotation cp="🇱🇹" type="tts">flag: Lithuania</annotation> + <annotation cp="🇱🇺">flag</annotation> + <annotation cp="🇱🇺" type="tts">flag: Luxembourg</annotation> + <annotation cp="🇱🇻">flag</annotation> + <annotation cp="🇱🇻" type="tts">flag: Latvia</annotation> + <annotation cp="🇱🇾">flag</annotation> + <annotation cp="🇱🇾" type="tts">flag: Libya</annotation> + <annotation cp="🇲🇦">flag</annotation> + <annotation cp="🇲🇦" type="tts">flag: Morocco</annotation> + <annotation cp="🇲🇨">flag</annotation> + <annotation cp="🇲🇨" type="tts">flag: Monaco</annotation> + <annotation cp="🇲🇩">flag</annotation> + <annotation cp="🇲🇩" type="tts">flag: Moldova</annotation> + <annotation cp="🇲🇪">flag</annotation> + <annotation cp="🇲🇪" type="tts">flag: Montenegro</annotation> + <annotation cp="🇲🇫">flag</annotation> + <annotation cp="🇲🇫" type="tts">flag: St. Martin</annotation> + <annotation cp="🇲🇬">flag</annotation> + <annotation cp="🇲🇬" type="tts">flag: Madagascar</annotation> + <annotation cp="🇲🇭">flag</annotation> + <annotation cp="🇲🇭" type="tts">flag: Marshall Islands</annotation> + <annotation cp="🇲🇰">flag</annotation> + <annotation cp="🇲🇰" type="tts">flag: North Macedonia</annotation> + <annotation cp="🇲🇱">flag</annotation> + <annotation cp="🇲🇱" type="tts">flag: Mali</annotation> + <annotation cp="🇲🇲">flag</annotation> + <annotation cp="🇲🇲" type="tts">flag: Myanmar (Burma)</annotation> + <annotation cp="🇲🇳">flag</annotation> + <annotation cp="🇲🇳" type="tts">flag: Mongolia</annotation> + <annotation cp="🇲🇴">flag</annotation> + <annotation cp="🇲🇴" type="tts">flag: Macao SAR China</annotation> + <annotation cp="🇲🇵">flag</annotation> + <annotation cp="🇲🇵" type="tts">flag: Northern Mariana Islands</annotation> + <annotation cp="🇲🇶">flag</annotation> + <annotation cp="🇲🇶" type="tts">flag: Martinique</annotation> + <annotation cp="🇲🇷">flag</annotation> + <annotation cp="🇲🇷" type="tts">flag: Mauritania</annotation> + <annotation cp="🇲🇸">flag</annotation> + <annotation cp="🇲🇸" type="tts">flag: Montserrat</annotation> + <annotation cp="🇲🇹">flag</annotation> + <annotation cp="🇲🇹" type="tts">flag: Malta</annotation> + <annotation cp="🇲🇺">flag</annotation> + <annotation cp="🇲🇺" type="tts">flag: Mauritius</annotation> + <annotation cp="🇲🇻">flag</annotation> + <annotation cp="🇲🇻" type="tts">flag: Maldives</annotation> + <annotation cp="🇲🇼">flag</annotation> + <annotation cp="🇲🇼" type="tts">flag: Malawi</annotation> + <annotation cp="🇲🇽">flag</annotation> + <annotation cp="🇲🇽" type="tts">flag: Mexico</annotation> + <annotation cp="🇲🇾">flag</annotation> + <annotation cp="🇲🇾" type="tts">flag: Malaysia</annotation> + <annotation cp="🇲🇿">flag</annotation> + <annotation cp="🇲🇿" type="tts">flag: Mozambique</annotation> + <annotation cp="🇳🇦">flag</annotation> + <annotation cp="🇳🇦" type="tts">flag: Namibia</annotation> + <annotation cp="🇳🇨">flag</annotation> + <annotation cp="🇳🇨" type="tts">flag: New Caledonia</annotation> + <annotation cp="🇳🇪">flag</annotation> + <annotation cp="🇳🇪" type="tts">flag: Niger</annotation> + <annotation cp="🇳🇫">flag</annotation> + <annotation cp="🇳🇫" type="tts">flag: Norfolk Island</annotation> + <annotation cp="🇳🇬">flag</annotation> + <annotation cp="🇳🇬" type="tts">flag: Nigeria</annotation> + <annotation cp="🇳🇮">flag</annotation> + <annotation cp="🇳🇮" type="tts">flag: Nicaragua</annotation> + <annotation cp="🇳🇱">flag</annotation> + <annotation cp="🇳🇱" type="tts">flag: Netherlands</annotation> + <annotation cp="🇳🇴">flag</annotation> + <annotation cp="🇳🇴" type="tts">flag: Norway</annotation> + <annotation cp="🇳🇵">flag</annotation> + <annotation cp="🇳🇵" type="tts">flag: Nepal</annotation> + <annotation cp="🇳🇷">flag</annotation> + <annotation cp="🇳🇷" type="tts">flag: Nauru</annotation> + <annotation cp="🇳🇺">flag</annotation> + <annotation cp="🇳🇺" type="tts">flag: Niue</annotation> + <annotation cp="🇳🇿">flag</annotation> + <annotation cp="🇳🇿" type="tts">flag: New Zealand</annotation> + <annotation cp="🇴🇲">flag</annotation> + <annotation cp="🇴🇲" type="tts">flag: Oman</annotation> + <annotation cp="🇵🇦">flag</annotation> + <annotation cp="🇵🇦" type="tts">flag: Panama</annotation> + <annotation cp="🇵🇪">flag</annotation> + <annotation cp="🇵🇪" type="tts">flag: Peru</annotation> + <annotation cp="🇵🇫">flag</annotation> + <annotation cp="🇵🇫" type="tts">flag: French Polynesia</annotation> + <annotation cp="🇵🇬">flag</annotation> + <annotation cp="🇵🇬" type="tts">flag: Papua New Guinea</annotation> + <annotation cp="🇵🇭">flag</annotation> + <annotation cp="🇵🇭" type="tts">flag: Philippines</annotation> + <annotation cp="🇵🇰">flag</annotation> + <annotation cp="🇵🇰" type="tts">flag: Pakistan</annotation> + <annotation cp="🇵🇱">flag</annotation> + <annotation cp="🇵🇱" type="tts">flag: Poland</annotation> + <annotation cp="🇵🇲">flag</annotation> + <annotation cp="🇵🇲" type="tts">flag: St. Pierre & Miquelon</annotation> + <annotation cp="🇵🇳">flag</annotation> + <annotation cp="🇵🇳" type="tts">flag: Pitcairn Islands</annotation> + <annotation cp="🇵🇷">flag</annotation> + <annotation cp="🇵🇷" type="tts">flag: Puerto Rico</annotation> + <annotation cp="🇵🇸">flag</annotation> + <annotation cp="🇵🇸" type="tts">flag: Palestinian Territories</annotation> + <annotation cp="🇵🇹">flag</annotation> + <annotation cp="🇵🇹" type="tts">flag: Portugal</annotation> + <annotation cp="🇵🇼">flag</annotation> + <annotation cp="🇵🇼" type="tts">flag: Palau</annotation> + <annotation cp="🇵🇾">flag</annotation> + <annotation cp="🇵🇾" type="tts">flag: Paraguay</annotation> + <annotation cp="🇶🇦">flag</annotation> + <annotation cp="🇶🇦" type="tts">flag: Qatar</annotation> + <annotation cp="🇷🇪">flag</annotation> + <annotation cp="🇷🇪" type="tts">flag: Réunion</annotation> + <annotation cp="🇷🇴">flag</annotation> + <annotation cp="🇷🇴" type="tts">flag: Romania</annotation> + <annotation cp="🇷🇸">flag</annotation> + <annotation cp="🇷🇸" type="tts">flag: Serbia</annotation> + <annotation cp="🇷🇺">flag</annotation> + <annotation cp="🇷🇺" type="tts">flag: Russia</annotation> + <annotation cp="🇷🇼">flag</annotation> + <annotation cp="🇷🇼" type="tts">flag: Rwanda</annotation> + <annotation cp="🇸🇦">flag</annotation> + <annotation cp="🇸🇦" type="tts">flag: Saudi Arabia</annotation> + <annotation cp="🇸🇧">flag</annotation> + <annotation cp="🇸🇧" type="tts">flag: Solomon Islands</annotation> + <annotation cp="🇸🇨">flag</annotation> + <annotation cp="🇸🇨" type="tts">flag: Seychelles</annotation> + <annotation cp="🇸🇩">flag</annotation> + <annotation cp="🇸🇩" type="tts">flag: Sudan</annotation> + <annotation cp="🇸🇪">flag</annotation> + <annotation cp="🇸🇪" type="tts">flag: Sweden</annotation> + <annotation cp="🇸🇬">flag</annotation> + <annotation cp="🇸🇬" type="tts">flag: Singapore</annotation> + <annotation cp="🇸🇭">flag</annotation> + <annotation cp="🇸🇭" type="tts">flag: St. Helena</annotation> + <annotation cp="🇸🇮">flag</annotation> + <annotation cp="🇸🇮" type="tts">flag: Slovenia</annotation> + <annotation cp="🇸🇯">flag</annotation> + <annotation cp="🇸🇯" type="tts">flag: Svalbard & Jan Mayen</annotation> + <annotation cp="🇸🇰">flag</annotation> + <annotation cp="🇸🇰" type="tts">flag: Slovakia</annotation> + <annotation cp="🇸🇱">flag</annotation> + <annotation cp="🇸🇱" type="tts">flag: Sierra Leone</annotation> + <annotation cp="🇸🇲">flag</annotation> + <annotation cp="🇸🇲" type="tts">flag: San Marino</annotation> + <annotation cp="🇸🇳">flag</annotation> + <annotation cp="🇸🇳" type="tts">flag: Senegal</annotation> + <annotation cp="🇸🇴">flag</annotation> + <annotation cp="🇸🇴" type="tts">flag: Somalia</annotation> + <annotation cp="🇸🇷">flag</annotation> + <annotation cp="🇸🇷" type="tts">flag: Suriname</annotation> + <annotation cp="🇸🇸">flag</annotation> + <annotation cp="🇸🇸" type="tts">flag: South Sudan</annotation> + <annotation cp="🇸🇹">flag</annotation> + <annotation cp="🇸🇹" type="tts">flag: São Tomé & Príncipe</annotation> + <annotation cp="🇸🇻">flag</annotation> + <annotation cp="🇸🇻" type="tts">flag: El Salvador</annotation> + <annotation cp="🇸🇽">flag</annotation> + <annotation cp="🇸🇽" type="tts">flag: Sint Maarten</annotation> + <annotation cp="🇸🇾">flag</annotation> + <annotation cp="🇸🇾" type="tts">flag: Syria</annotation> + <annotation cp="🇸🇿">flag</annotation> + <annotation cp="🇸🇿" type="tts">flag: Eswatini</annotation> + <annotation cp="🇹🇦">flag</annotation> + <annotation cp="🇹🇦" type="tts">flag: Tristan da Cunha</annotation> + <annotation cp="🇹🇨">flag</annotation> + <annotation cp="🇹🇨" type="tts">flag: Turks & Caicos Islands</annotation> + <annotation cp="🇹🇩">flag</annotation> + <annotation cp="🇹🇩" type="tts">flag: Chad</annotation> + <annotation cp="🇹🇫">flag</annotation> + <annotation cp="🇹🇫" type="tts">flag: French Southern Territories</annotation> + <annotation cp="🇹🇬">flag</annotation> + <annotation cp="🇹🇬" type="tts">flag: Togo</annotation> + <annotation cp="🇹🇭">flag</annotation> + <annotation cp="🇹🇭" type="tts">flag: Thailand</annotation> + <annotation cp="🇹🇯">flag</annotation> + <annotation cp="🇹🇯" type="tts">flag: Tajikistan</annotation> + <annotation cp="🇹🇰">flag</annotation> + <annotation cp="🇹🇰" type="tts">flag: Tokelau</annotation> + <annotation cp="🇹🇱">flag</annotation> + <annotation cp="🇹🇱" type="tts">flag: Timor-Leste</annotation> + <annotation cp="🇹🇲">flag</annotation> + <annotation cp="🇹🇲" type="tts">flag: Turkmenistan</annotation> + <annotation cp="🇹🇳">flag</annotation> + <annotation cp="🇹🇳" type="tts">flag: Tunisia</annotation> + <annotation cp="🇹🇴">flag</annotation> + <annotation cp="🇹🇴" type="tts">flag: Tonga</annotation> + <annotation cp="🇹🇷">flag</annotation> + <annotation cp="🇹🇷" type="tts">flag: Turkey</annotation> + <annotation cp="🇹🇹">flag</annotation> + <annotation cp="🇹🇹" type="tts">flag: Trinidad & Tobago</annotation> + <annotation cp="🇹🇻">flag</annotation> + <annotation cp="🇹🇻" type="tts">flag: Tuvalu</annotation> + <annotation cp="🇹🇼">flag</annotation> + <annotation cp="🇹🇼" type="tts">flag: Taiwan</annotation> + <annotation cp="🇹🇿">flag</annotation> + <annotation cp="🇹🇿" type="tts">flag: Tanzania</annotation> + <annotation cp="🇺🇦">flag</annotation> + <annotation cp="🇺🇦" type="tts">flag: Ukraine</annotation> + <annotation cp="🇺🇬">flag</annotation> + <annotation cp="🇺🇬" type="tts">flag: Uganda</annotation> + <annotation cp="🇺🇲">flag</annotation> + <annotation cp="🇺🇲" type="tts">flag: U.S. Outlying Islands</annotation> + <annotation cp="🇺🇳">flag</annotation> + <annotation cp="🇺🇳" type="tts">flag: United Nations</annotation> + <annotation cp="🇺🇸">flag</annotation> + <annotation cp="🇺🇸" type="tts">flag: United States</annotation> + <annotation cp="🇺🇾">flag</annotation> + <annotation cp="🇺🇾" type="tts">flag: Uruguay</annotation> + <annotation cp="🇺🇿">flag</annotation> + <annotation cp="🇺🇿" type="tts">flag: Uzbekistan</annotation> + <annotation cp="🇻🇦">flag</annotation> + <annotation cp="🇻🇦" type="tts">flag: Vatican City</annotation> + <annotation cp="🇻🇨">flag</annotation> + <annotation cp="🇻🇨" type="tts">flag: St. Vincent & Grenadines</annotation> + <annotation cp="🇻🇪">flag</annotation> + <annotation cp="🇻🇪" type="tts">flag: Venezuela</annotation> + <annotation cp="🇻🇬">flag</annotation> + <annotation cp="🇻🇬" type="tts">flag: British Virgin Islands</annotation> + <annotation cp="🇻🇮">flag</annotation> + <annotation cp="🇻🇮" type="tts">flag: U.S. Virgin Islands</annotation> + <annotation cp="🇻🇳">flag</annotation> + <annotation cp="🇻🇳" type="tts">flag: Vietnam</annotation> + <annotation cp="🇻🇺">flag</annotation> + <annotation cp="🇻🇺" type="tts">flag: Vanuatu</annotation> + <annotation cp="🇼🇫">flag</annotation> + <annotation cp="🇼🇫" type="tts">flag: Wallis & Futuna</annotation> + <annotation cp="🇼🇸">flag</annotation> + <annotation cp="🇼🇸" type="tts">flag: Samoa</annotation> + <annotation cp="🇽🇰">flag</annotation> + <annotation cp="🇽🇰" type="tts">flag: Kosovo</annotation> + <annotation cp="🇾🇪">flag</annotation> + <annotation cp="🇾🇪" type="tts">flag: Yemen</annotation> + <annotation cp="🇾🇹">flag</annotation> + <annotation cp="🇾🇹" type="tts">flag: Mayotte</annotation> + <annotation cp="🇿🇦">flag</annotation> + <annotation cp="🇿🇦" type="tts">flag: South Africa</annotation> + <annotation cp="🇿🇲">flag</annotation> + <annotation cp="🇿🇲" type="tts">flag: Zambia</annotation> + <annotation cp="🇿🇼">flag</annotation> + <annotation cp="🇿🇼" type="tts">flag: Zimbabwe</annotation> + <annotation cp="🏴">flag</annotation> + <annotation cp="🏴" type="tts">flag: England</annotation> + <annotation cp="🏴">flag</annotation> + <annotation cp="🏴" type="tts">flag: Scotland</annotation> + <annotation cp="🏴">flag</annotation> + <annotation cp="🏴" type="tts">flag: Wales</annotation> + <annotation cp="¤" type="tts">Unknown Currency</annotation> + <annotation cp="֏" type="tts">Armenian Dram</annotation> + <annotation cp="؋" type="tts">Afghan Afghani</annotation> + <annotation cp="৳" type="tts">Bangladeshi Taka</annotation> + <annotation cp="฿" type="tts">Thai Baht</annotation> + <annotation cp="៛" type="tts">Cambodian Riel</annotation> + <annotation cp="₡" type="tts">Costa Rican Colón</annotation> + <annotation cp="₦" type="tts">Nigerian Naira</annotation> + <annotation cp="₧" type="tts">Spanish Peseta</annotation> + <annotation cp="₪" type="tts">Israeli New Shekel</annotation> + <annotation cp="₫" type="tts">Vietnamese Dong</annotation> + <annotation cp="₭" type="tts">Laotian Kip</annotation> + <annotation cp="₮" type="tts">Mongolian Tugrik</annotation> + <annotation cp="₲" type="tts">Paraguayan Guarani</annotation> + <annotation cp="₴" type="tts">Ukrainian Hryvnia</annotation> + <annotation cp="₵" type="tts">Ghanaian Cedi</annotation> + <annotation cp="₸" type="tts">Kazakhstani Tenge</annotation> + <annotation cp="₺" type="tts">Turkish Lira</annotation> + <annotation cp="₼" type="tts">Azerbaijani Manat</annotation> + <annotation cp="₾" type="tts">Georgian Lari</annotation> + <annotation cp="0⃣">keycap</annotation> + <annotation cp="0⃣" type="tts">keycap: 0</annotation> + <annotation cp="1⃣">keycap</annotation> + <annotation cp="1⃣" type="tts">keycap: 1</annotation> + <annotation cp="2⃣">keycap</annotation> + <annotation cp="2⃣" type="tts">keycap: 2</annotation> + <annotation cp="3⃣">keycap</annotation> + <annotation cp="3⃣" type="tts">keycap: 3</annotation> + <annotation cp="4⃣">keycap</annotation> + <annotation cp="4⃣" type="tts">keycap: 4</annotation> + <annotation cp="5⃣">keycap</annotation> + <annotation cp="5⃣" type="tts">keycap: 5</annotation> + <annotation cp="6⃣">keycap</annotation> + <annotation cp="6⃣" type="tts">keycap: 6</annotation> + <annotation cp="7⃣">keycap</annotation> + <annotation cp="7⃣" type="tts">keycap: 7</annotation> + <annotation cp="8⃣">keycap</annotation> + <annotation cp="8⃣" type="tts">keycap: 8</annotation> + <annotation cp="9⃣">keycap</annotation> + <annotation cp="9⃣" type="tts">keycap: 9</annotation> + </annotations> +</ldml>
diff --git a/third_party/cldr/src/common/annotationsDerived/en_001.xml b/third_party/cldr/src/common/annotationsDerived/en_001.xml new file mode 100644 index 0000000..12d005a --- /dev/null +++ b/third_party/cldr/src/common/annotationsDerived/en_001.xml
@@ -0,0 +1,295 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE ldml SYSTEM "../../common/dtd/ldml.dtd"> +<!-- Copyright © 1991-2020 Unicode, Inc. +For terms of use, see http://www.unicode.org/copyright.html +Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/) + +Derived short names and annotations, using GenerateDerivedAnnotations.java. See warnings in /annotations/ file. +--> +<ldml> + <identity> + <version number="$Revision$"/> + <language type="en"/> + <territory type="001"/> + </identity> + <annotations> + <annotation cp="✌🏻">hand | light skin tone | peace hand | peace sign | v | v sign | victory</annotation> + <annotation cp="✌🏼">hand | medium-light skin tone | peace hand | peace sign | v | v sign | victory</annotation> + <annotation cp="✌🏽">hand | medium skin tone | peace hand | peace sign | v | v sign | victory</annotation> + <annotation cp="✌🏾">hand | medium-dark skin tone | peace hand | peace sign | v | v sign | victory</annotation> + <annotation cp="✌🏿">dark skin tone | hand | peace hand | peace sign | v | v sign | victory</annotation> + <annotation cp="🤘🏻">finger | hand | horns | light skin tone | rock on</annotation> + <annotation cp="🤘🏼">finger | hand | horns | medium-light skin tone | rock on</annotation> + <annotation cp="🤘🏽">finger | hand | horns | medium skin tone | rock on</annotation> + <annotation cp="🤘🏾">finger | hand | horns | medium-dark skin tone | rock on</annotation> + <annotation cp="🤘🏿">dark skin tone | finger | hand | horns | rock on</annotation> + <annotation cp="🤙🏻">call | call-me hand | hand | light skin tone</annotation> + <annotation cp="🤙🏻" type="tts">call-me hand: light skin tone</annotation> + <annotation cp="🤙🏼">call | call-me hand | hand | medium-light skin tone</annotation> + <annotation cp="🤙🏼" type="tts">call-me hand: medium-light skin tone</annotation> + <annotation cp="🤙🏽">call | call-me hand | hand | medium skin tone</annotation> + <annotation cp="🤙🏽" type="tts">call-me hand: medium skin tone</annotation> + <annotation cp="🤙🏾">call | call-me hand | hand | medium-dark skin tone</annotation> + <annotation cp="🤙🏾" type="tts">call-me hand: medium-dark skin tone</annotation> + <annotation cp="🤙🏿">call | call-me hand | dark skin tone | hand</annotation> + <annotation cp="🤙🏿" type="tts">call-me hand: dark skin tone</annotation> + <annotation cp="🙌🏻">celebration | gesture | hand | hooray | light skin tone | raised | raising hands | woo hoo | yay</annotation> + <annotation cp="🙌🏼">celebration | gesture | hand | hooray | medium-light skin tone | raised | raising hands | woo hoo | yay</annotation> + <annotation cp="🙌🏽">celebration | gesture | hand | hooray | medium skin tone | raised | raising hands | woo hoo | yay</annotation> + <annotation cp="🙌🏾">celebration | gesture | hand | hooray | medium-dark skin tone | raised | raising hands | woo hoo | yay</annotation> + <annotation cp="🙌🏿">celebration | dark skin tone | gesture | hand | hooray | raised | raising hands | woo hoo | yay</annotation> + <annotation cp="🧒🏻">child | gender-neutral | light skin tone | toddler | young</annotation> + <annotation cp="🧒🏼">child | gender-neutral | medium-light skin tone | toddler | young</annotation> + <annotation cp="🧒🏽">child | gender-neutral | medium skin tone | toddler | young</annotation> + <annotation cp="🧒🏾">child | gender-neutral | medium-dark skin tone | toddler | young</annotation> + <annotation cp="🧒🏿">child | dark skin tone | gender-neutral | toddler | young</annotation> + <annotation cp="🙇🏻♂">apology | bowing | favour | gesture | light skin tone | man | sorry</annotation> + <annotation cp="🙇🏼♂">apology | bowing | favour | gesture | man | medium-light skin tone | sorry</annotation> + <annotation cp="🙇🏽♂">apology | bowing | favour | gesture | man | medium skin tone | sorry</annotation> + <annotation cp="🙇🏾♂">apology | bowing | favour | gesture | man | medium-dark skin tone | sorry</annotation> + <annotation cp="🙇🏿♂">apology | bowing | dark skin tone | favour | gesture | man | sorry</annotation> + <annotation cp="🙇🏻♀">apology | bowing | favour | gesture | light skin tone | sorry | woman</annotation> + <annotation cp="🙇🏼♀">apology | bowing | favour | gesture | medium-light skin tone | sorry | woman</annotation> + <annotation cp="🙇🏽♀">apology | bowing | favour | gesture | medium skin tone | sorry | woman</annotation> + <annotation cp="🙇🏾♀">apology | bowing | favour | gesture | medium-dark skin tone | sorry | woman</annotation> + <annotation cp="🙇🏿♀">apology | bowing | dark skin tone | favour | gesture | sorry | woman</annotation> + <annotation cp="🧑🏻🏫">instructor | lecturer | light skin tone | professor | teacher</annotation> + <annotation cp="🧑🏼🏫">instructor | lecturer | medium-light skin tone | professor | teacher</annotation> + <annotation cp="🧑🏽🏫">instructor | lecturer | medium skin tone | professor | teacher</annotation> + <annotation cp="🧑🏾🏫">instructor | lecturer | medium-dark skin tone | professor | teacher</annotation> + <annotation cp="🧑🏿🏫">dark skin tone | instructor | lecturer | professor | teacher</annotation> + <annotation cp="🧑🏻⚖">judge | law | light skin tone</annotation> + <annotation cp="🧑🏼⚖">judge | law | medium-light skin tone</annotation> + <annotation cp="🧑🏽⚖">judge | law | medium skin tone</annotation> + <annotation cp="🧑🏾⚖">judge | law | medium-dark skin tone</annotation> + <annotation cp="🧑🏿⚖">dark skin tone | judge | law</annotation> + <annotation cp="👨🏻🌾">farmer | gardener | light skin tone | man</annotation> + <annotation cp="👨🏼🌾">farmer | gardener | man | medium-light skin tone</annotation> + <annotation cp="👨🏽🌾">farmer | gardener | man | medium skin tone</annotation> + <annotation cp="👨🏾🌾">farmer | gardener | man | medium-dark skin tone</annotation> + <annotation cp="👨🏿🌾">dark skin tone | farmer | gardener | man</annotation> + <annotation cp="👩🏻🌾">farmer | gardener | light skin tone | woman</annotation> + <annotation cp="👩🏼🌾">farmer | gardener | medium-light skin tone | woman</annotation> + <annotation cp="👩🏽🌾">farmer | gardener | medium skin tone | woman</annotation> + <annotation cp="👩🏾🌾">farmer | gardener | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏿🌾">dark skin tone | farmer | gardener | woman</annotation> + <annotation cp="👨🏻🔧">electrician | light skin tone | man | mechanic | plumber | tradesman | tradesperson</annotation> + <annotation cp="👨🏼🔧">electrician | man | mechanic | medium-light skin tone | plumber | tradesman | tradesperson</annotation> + <annotation cp="👨🏽🔧">electrician | man | mechanic | medium skin tone | plumber | tradesman | tradesperson</annotation> + <annotation cp="👨🏾🔧">electrician | man | mechanic | medium-dark skin tone | plumber | tradesman | tradesperson</annotation> + <annotation cp="👨🏿🔧">dark skin tone | electrician | man | mechanic | plumber | tradesman | tradesperson</annotation> + <annotation cp="👩🏻🔧">electrician | light skin tone | mechanic | plumber | tradesperson | tradeswoman | woman</annotation> + <annotation cp="👩🏼🔧">electrician | mechanic | medium-light skin tone | plumber | tradesperson | tradeswoman | woman</annotation> + <annotation cp="👩🏽🔧">electrician | mechanic | medium skin tone | plumber | tradesperson | tradeswoman | woman</annotation> + <annotation cp="👩🏾🔧">electrician | mechanic | medium-dark skin tone | plumber | tradesperson | tradeswoman | woman</annotation> + <annotation cp="👩🏿🔧">dark skin tone | electrician | mechanic | plumber | tradesperson | tradeswoman | woman</annotation> + <annotation cp="🧑🏻🚒">fire engine | fire truck | firefighter | light skin tone</annotation> + <annotation cp="🧑🏼🚒">fire engine | fire truck | firefighter | medium-light skin tone</annotation> + <annotation cp="🧑🏽🚒">fire engine | fire truck | firefighter | medium skin tone</annotation> + <annotation cp="🧑🏾🚒">fire engine | fire truck | firefighter | medium-dark skin tone</annotation> + <annotation cp="🧑🏿🚒">dark skin tone | fire engine | fire truck | firefighter</annotation> + <annotation cp="👨🏻🚒">fire engine | firefighter | fireman | light skin tone | man</annotation> + <annotation cp="👨🏼🚒">fire engine | firefighter | fireman | man | medium-light skin tone</annotation> + <annotation cp="👨🏽🚒">fire engine | firefighter | fireman | man | medium skin tone</annotation> + <annotation cp="👨🏾🚒">fire engine | firefighter | fireman | man | medium-dark skin tone</annotation> + <annotation cp="👨🏿🚒">dark skin tone | fire engine | firefighter | fireman | man</annotation> + <annotation cp="👩🏻🚒">fire engine | firefighter | firewoman | light skin tone | woman</annotation> + <annotation cp="👩🏼🚒">fire engine | firefighter | firewoman | medium-light skin tone | woman</annotation> + <annotation cp="👩🏽🚒">fire engine | firefighter | firewoman | medium skin tone | woman</annotation> + <annotation cp="👩🏾🚒">fire engine | firefighter | firewoman | medium-dark skin tone | woman</annotation> + <annotation cp="👩🏿🚒">dark skin tone | fire engine | firefighter | firewoman | woman</annotation> + <annotation cp="👮🏻♂">cop | light skin tone | man | officer | police | policeman</annotation> + <annotation cp="👮🏼♂">cop | man | medium-light skin tone | officer | police | policeman</annotation> + <annotation cp="👮🏽♂">cop | man | medium skin tone | officer | police | policeman</annotation> + <annotation cp="👮🏾♂">cop | man | medium-dark skin tone | officer | police | policeman</annotation> + <annotation cp="👮🏿♂">cop | dark skin tone | man | officer | police | policeman</annotation> + <annotation cp="👮🏻♀">cop | light skin tone | officer | police | policewoman | woman</annotation> + <annotation cp="👮🏼♀">cop | medium-light skin tone | officer | police | policewoman | woman</annotation> + <annotation cp="👮🏽♀">cop | medium skin tone | officer | police | policewoman | woman</annotation> + <annotation cp="👮🏾♀">cop | medium-dark skin tone | officer | police | policewoman | woman</annotation> + <annotation cp="👮🏿♀">cop | dark skin tone | officer | police | policewoman | woman</annotation> + <annotation cp="💂🏻♂">guard | guardsman | light skin tone | man</annotation> + <annotation cp="💂🏼♂">guard | guardsman | man | medium-light skin tone</annotation> + <annotation cp="💂🏽♂">guard | guardsman | man | medium skin tone</annotation> + <annotation cp="💂🏾♂">guard | guardsman | man | medium-dark skin tone</annotation> + <annotation cp="💂🏿♂">dark skin tone | guard | guardsman | man</annotation> + <annotation cp="💂🏻♀">guard | guardswoman | light skin tone | woman</annotation> + <annotation cp="💂🏼♀">guard | guardswoman | medium-light skin tone | woman</annotation> + <annotation cp="💂🏽♀">guard | guardswoman | medium skin tone | woman</annotation> + <annotation cp="💂🏾♀">guard | guardswoman | medium-dark skin tone | woman</annotation> + <annotation cp="💂🏿♀">dark skin tone | guard | guardswoman | woman</annotation> + <annotation cp="👷🏻">builder | construction | hat | light skin tone | worker</annotation> + <annotation cp="👷🏼">builder | construction | hat | medium-light skin tone | worker</annotation> + <annotation cp="👷🏽">builder | construction | hat | medium skin tone | worker</annotation> + <annotation cp="👷🏾">builder | construction | hat | medium-dark skin tone | worker</annotation> + <annotation cp="👷🏿">builder | construction | dark skin tone | hat | worker</annotation> + <annotation cp="👷🏻♂">builder | construction | light skin tone | man | worker</annotation> + <annotation cp="👷🏼♂">builder | construction | man | medium-light skin tone | worker</annotation> + <annotation cp="👷🏽♂">builder | construction | man | medium skin tone | worker</annotation> + <annotation cp="👷🏾♂">builder | construction | man | medium-dark skin tone | worker</annotation> + <annotation cp="👷🏿♂">builder | construction | dark skin tone | man | worker</annotation> + <annotation cp="👷🏻♀">builder | construction | light skin tone | woman | worker</annotation> + <annotation cp="👷🏼♀">builder | construction | medium-light skin tone | woman | worker</annotation> + <annotation cp="👷🏽♀">builder | construction | medium skin tone | woman | worker</annotation> + <annotation cp="👷🏾♀">builder | construction | medium-dark skin tone | woman | worker</annotation> + <annotation cp="👷🏿♀">builder | construction | dark skin tone | woman | worker</annotation> + <annotation cp="👲🏻">gua pi mao | hat | light skin tone | man | man with Chinese cap | skullcap</annotation> + <annotation cp="👲🏼">gua pi mao | hat | man | man with Chinese cap | medium-light skin tone | skullcap</annotation> + <annotation cp="👲🏽">gua pi mao | hat | man | man with Chinese cap | medium skin tone | skullcap</annotation> + <annotation cp="👲🏾">gua pi mao | hat | man | man with Chinese cap | medium-dark skin tone | skullcap</annotation> + <annotation cp="👲🏿">dark skin tone | gua pi mao | hat | man | man with Chinese cap | skullcap</annotation> + <annotation cp="🤵🏻">groom | light skin tone | person | person in tux | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏼">groom | medium-light skin tone | person | person in tux | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏽">groom | medium skin tone | person | person in tux | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏾">groom | medium-dark skin tone | person | person in tux | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏿">dark skin tone | groom | person | person in tux | person in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏻♂">light skin tone | man | man in tux | man in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏼♂">man | man in tux | man in tuxedo | medium-light skin tone | tuxedo</annotation> + <annotation cp="🤵🏽♂">man | man in tux | man in tuxedo | medium skin tone | tuxedo</annotation> + <annotation cp="🤵🏾♂">man | man in tux | man in tuxedo | medium-dark skin tone | tuxedo</annotation> + <annotation cp="🤵🏿♂">dark skin tone | man | man in tux | man in tuxedo | tuxedo</annotation> + <annotation cp="🤵🏻♀">light skin tone | tuxedo | woman | woman in tux | woman in tuxedo</annotation> + <annotation cp="🤵🏼♀">medium-light skin tone | tuxedo | woman | woman in tux | woman in tuxedo</annotation> + <annotation cp="🤵🏽♀">medium skin tone | tuxedo | woman | woman in tux | woman in tuxedo</annotation> + <annotation cp="🤵🏾♀">medium-dark skin tone | tuxedo | woman | woman in tux | woman in tuxedo</annotation> + <annotation cp="🤵🏿♀">dark skin tone | tuxedo | woman | woman in tux | woman in tuxedo</annotation> + <annotation cp="🤱🏻">baby | breast | breastfeeding | light skin tone | nursing</annotation> + <annotation cp="🤱🏻" type="tts">breastfeeding: light skin tone</annotation> + <annotation cp="🤱🏼">baby | breast | breastfeeding | medium-light skin tone | nursing</annotation> + <annotation cp="🤱🏼" type="tts">breastfeeding: medium-light skin tone</annotation> + <annotation cp="🤱🏽">baby | breast | breastfeeding | medium skin tone | nursing</annotation> + <annotation cp="🤱🏽" type="tts">breastfeeding: medium skin tone</annotation> + <annotation cp="🤱🏾">baby | breast | breastfeeding | medium-dark skin tone | nursing</annotation> + <annotation cp="🤱🏾" type="tts">breastfeeding: medium-dark skin tone</annotation> + <annotation cp="🤱🏿">baby | breast | breastfeeding | dark skin tone | nursing</annotation> + <annotation cp="🤱🏿" type="tts">breastfeeding: dark skin tone</annotation> + <annotation cp="🎅🏻">celebration | Christmas | claus | father | Father Christmas | light skin tone | santa | Santa Claus</annotation> + <annotation cp="🎅🏼">celebration | Christmas | claus | father | Father Christmas | medium-light skin tone | santa | Santa Claus</annotation> + <annotation cp="🎅🏽">celebration | Christmas | claus | father | Father Christmas | medium skin tone | santa | Santa Claus</annotation> + <annotation cp="🎅🏾">celebration | Christmas | claus | father | Father Christmas | medium-dark skin tone | santa | Santa Claus</annotation> + <annotation cp="🎅🏿">celebration | Christmas | claus | dark skin tone | father | Father Christmas | santa | Santa Claus</annotation> + <annotation cp="🤶🏻">celebration | Christmas | claus | light skin tone | mother | Mrs | Mrs Claus</annotation> + <annotation cp="🤶🏻" type="tts">Mrs Claus: light skin tone</annotation> + <annotation cp="🤶🏼">celebration | Christmas | claus | medium-light skin tone | mother | Mrs | Mrs Claus</annotation> + <annotation cp="🤶🏼" type="tts">Mrs Claus: medium-light skin tone</annotation> + <annotation cp="🤶🏽">celebration | Christmas | claus | medium skin tone | mother | Mrs | Mrs Claus</annotation> + <annotation cp="🤶🏽" type="tts">Mrs Claus: medium skin tone</annotation> + <annotation cp="🤶🏾">celebration | Christmas | claus | medium-dark skin tone | mother | Mrs | Mrs Claus</annotation> + <annotation cp="🤶🏾" type="tts">Mrs Claus: medium-dark skin tone</annotation> + <annotation cp="🤶🏿">celebration | Christmas | claus | dark skin tone | mother | Mrs | Mrs Claus</annotation> + <annotation cp="🤶🏿" type="tts">Mrs Claus: dark skin tone</annotation> + <annotation cp="💇🏻">barber | beauty | haircut | hairdresser | light skin tone | parlour</annotation> + <annotation cp="💇🏼">barber | beauty | haircut | hairdresser | medium-light skin tone | parlour</annotation> + <annotation cp="💇🏽">barber | beauty | haircut | hairdresser | medium skin tone | parlour</annotation> + <annotation cp="💇🏾">barber | beauty | haircut | hairdresser | medium-dark skin tone | parlour</annotation> + <annotation cp="💇🏿">barber | beauty | dark skin tone | haircut | hairdresser | parlour</annotation> + <annotation cp="🧑🏻🦯">accessibility | blind | light skin tone | person with guide cane</annotation> + <annotation cp="🧑🏻🦯" type="tts">person with guide cane: light skin tone</annotation> + <annotation cp="🧑🏼🦯">accessibility | blind | medium-light skin tone | person with guide cane</annotation> + <annotation cp="🧑🏼🦯" type="tts">person with guide cane: medium-light skin tone</annotation> + <annotation cp="🧑🏽🦯">accessibility | blind | medium skin tone | person with guide cane</annotation> + <annotation cp="🧑🏽🦯" type="tts">person with guide cane: medium skin tone</annotation> + <annotation cp="🧑🏾🦯">accessibility | blind | medium-dark skin tone | person with guide cane</annotation> + <annotation cp="🧑🏾🦯" type="tts">person with guide cane: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🦯">accessibility | blind | dark skin tone | person with guide cane</annotation> + <annotation cp="🧑🏿🦯" type="tts">person with guide cane: dark skin tone</annotation> + <annotation cp="👨🏻🦯">accessibility | blind | light skin tone | man | man with guide cane</annotation> + <annotation cp="👨🏻🦯" type="tts">man with guide cane: light skin tone</annotation> + <annotation cp="👨🏼🦯">accessibility | blind | man | man with guide cane | medium-light skin tone</annotation> + <annotation cp="👨🏼🦯" type="tts">man with guide cane: medium-light skin tone</annotation> + <annotation cp="👨🏽🦯">accessibility | blind | man | man with guide cane | medium skin tone</annotation> + <annotation cp="👨🏽🦯" type="tts">man with guide cane: medium skin tone</annotation> + <annotation cp="👨🏾🦯">accessibility | blind | man | man with guide cane | medium-dark skin tone</annotation> + <annotation cp="👨🏾🦯" type="tts">man with guide cane: medium-dark skin tone</annotation> + <annotation cp="👨🏿🦯">accessibility | blind | dark skin tone | man | man with guide cane</annotation> + <annotation cp="👨🏿🦯" type="tts">man with guide cane: dark skin tone</annotation> + <annotation cp="👩🏻🦯">accessibility | blind | light skin tone | woman | woman with guide cane</annotation> + <annotation cp="👩🏻🦯" type="tts">woman with guide cane: light skin tone</annotation> + <annotation cp="👩🏼🦯">accessibility | blind | medium-light skin tone | woman | woman with guide cane</annotation> + <annotation cp="👩🏼🦯" type="tts">woman with guide cane: medium-light skin tone</annotation> + <annotation cp="👩🏽🦯">accessibility | blind | medium skin tone | woman | woman with guide cane</annotation> + <annotation cp="👩🏽🦯" type="tts">woman with guide cane: medium skin tone</annotation> + <annotation cp="👩🏾🦯">accessibility | blind | medium-dark skin tone | woman | woman with guide cane</annotation> + <annotation cp="👩🏾🦯" type="tts">woman with guide cane: medium-dark skin tone</annotation> + <annotation cp="👩🏿🦯">accessibility | blind | dark skin tone | woman | woman with guide cane</annotation> + <annotation cp="👩🏿🦯" type="tts">woman with guide cane: dark skin tone</annotation> + <annotation cp="🧑🏻🦼">accessibility | light skin tone | person in powered wheelchair | wheelchair</annotation> + <annotation cp="🧑🏻🦼" type="tts">person in powered wheelchair: light skin tone</annotation> + <annotation cp="🧑🏼🦼">accessibility | medium-light skin tone | person in powered wheelchair | wheelchair</annotation> + <annotation cp="🧑🏼🦼" type="tts">person in powered wheelchair: medium-light skin tone</annotation> + <annotation cp="🧑🏽🦼">accessibility | medium skin tone | person in powered wheelchair | wheelchair</annotation> + <annotation cp="🧑🏽🦼" type="tts">person in powered wheelchair: medium skin tone</annotation> + <annotation cp="🧑🏾🦼">accessibility | medium-dark skin tone | person in powered wheelchair | wheelchair</annotation> + <annotation cp="🧑🏾🦼" type="tts">person in powered wheelchair: medium-dark skin tone</annotation> + <annotation cp="🧑🏿🦼">accessibility | dark skin tone | person in powered wheelchair | wheelchair</annotation> + <annotation cp="🧑🏿🦼" type="tts">person in powered wheelchair: dark skin tone</annotation> + <annotation cp="👨🏻🦼">accessibility | light skin tone | man | man in powered wheelchair | wheelchair</annotation> + <annotation cp="👨🏻🦼" type="tts">man in powered wheelchair: light skin tone</annotation> + <annotation cp="👨🏼🦼">accessibility | man | man in powered wheelchair | medium-light skin tone | wheelchair</annotation> + <annotation cp="👨🏼🦼" type="tts">man in powered wheelchair: medium-light skin tone</annotation> + <annotation cp="👨🏽🦼">accessibility | man | man in powered wheelchair | medium skin tone | wheelchair</annotation> + <annotation cp="👨🏽🦼" type="tts">man in powered wheelchair: medium skin tone</annotation> + <annotation cp="👨🏾🦼">accessibility | man | man in powered wheelchair | medium-dark skin tone | wheelchair</annotation> + <annotation cp="👨🏾🦼" type="tts">man in powered wheelchair: medium-dark skin tone</annotation> + <annotation cp="👨🏿🦼">accessibility | dark skin tone | man | man in powered wheelchair | wheelchair</annotation> + <annotation cp="👨🏿🦼" type="tts">man in powered wheelchair: dark skin tone</annotation> + <annotation cp="👩🏻🦼">accessibility | light skin tone | wheelchair | woman | woman in powered wheelchair</annotation> + <annotation cp="👩🏻🦼" type="tts">woman in powered wheelchair: light skin tone</annotation> + <annotation cp="👩🏼🦼">accessibility | medium-light skin tone | wheelchair | woman | woman in powered wheelchair</annotation> + <annotation cp="👩🏼🦼" type="tts">woman in powered wheelchair: medium-light skin tone</annotation> + <annotation cp="👩🏽🦼">accessibility | medium skin tone | wheelchair | woman | woman in powered wheelchair</annotation> + <annotation cp="👩🏽🦼" type="tts">woman in powered wheelchair: medium skin tone</annotation> + <annotation cp="👩🏾🦼">accessibility | medium-dark skin tone | wheelchair | woman | woman in powered wheelchair</annotation> + <annotation cp="👩🏾🦼" type="tts">woman in powered wheelchair: medium-dark skin tone</annotation> + <annotation cp="👩🏿🦼">accessibility | dark skin tone | wheelchair | woman | woman in powered wheelchair</annotation> + <annotation cp="👩🏿🦼" type="tts">woman in powered wheelchair: dark skin tone</annotation> + <annotation cp="🚣🏻">boat | light skin tone | rowboat | rowing boat</annotation> + <annotation cp="🚣🏼">boat | medium-light skin tone | rowboat | rowing boat</annotation> + <annotation cp="🚣🏽">boat | medium skin tone | rowboat | rowing boat</annotation> + <annotation cp="🚣🏾">boat | medium-dark skin tone | rowboat | rowing boat</annotation> + <annotation cp="🚣🏿">boat | dark skin tone | rowboat | rowing boat</annotation> + <annotation cp="🚣🏻♂">boat | light skin tone | man | rowboat | rowing boat</annotation> + <annotation cp="🚣🏼♂">boat | man | medium-light skin tone | rowboat | rowing boat</annotation> + <annotation cp="🚣🏽♂">boat | man | medium skin tone | rowboat | rowing boat</annotation> + <annotation cp="🚣🏾♂">boat | man | medium-dark skin tone | rowboat | rowing boat</annotation> + <annotation cp="🚣🏿♂">boat | dark skin tone | man | rowboat | rowing boat</annotation> + <annotation cp="🚣🏻♀">boat | light skin tone | rowboat | rowing boat | woman</annotation> + <annotation cp="🚣🏼♀">boat | medium-light skin tone | rowboat | rowing boat | woman</annotation> + <annotation cp="🚣🏽♀">boat | medium skin tone | rowboat | rowing boat | woman</annotation> + <annotation cp="🚣🏾♀">boat | medium-dark skin tone | rowboat | rowing boat | woman</annotation> + <annotation cp="🚣🏿♀">boat | dark skin tone | rowboat | rowing boat | woman</annotation> + <annotation cp="🏋🏻">light skin tone | person lifting weights | weight | weightlifter</annotation> + <annotation cp="🏋🏼">medium-light skin tone | person lifting weights | weight | weightlifter</annotation> + <annotation cp="🏋🏽">medium skin tone | person lifting weights | weight | weightlifter</annotation> + <annotation cp="🏋🏾">medium-dark skin tone | person lifting weights | weight | weightlifter</annotation> + <annotation cp="🏋🏿">dark skin tone | person lifting weights | weight | weightlifter</annotation> + <annotation cp="🏋🏻♂">light skin tone | man | weightlifter</annotation> + <annotation cp="🏋🏼♂">man | medium-light skin tone | weightlifter</annotation> + <annotation cp="🏋🏽♂">man | medium skin tone | weightlifter</annotation> + <annotation cp="🏋🏾♂">man | medium-dark skin tone | weightlifter</annotation> + <annotation cp="🏋🏿♂">dark skin tone | man | weightlifter</annotation> + <annotation cp="🏋🏻♀">light skin tone | weightlifter | woman</annotation> + <annotation cp="🏋🏼♀">medium-light skin tone | weightlifter | woman</annotation> + <annotation cp="🏋🏽♀">medium skin tone | weightlifter | woman</annotation> + <annotation cp="🏋🏾♀">medium-dark skin tone | weightlifter | woman</annotation> + <annotation cp="🏋🏿♀">dark skin tone | weightlifter | woman</annotation> + <annotation cp="🚴🏻">bicycle | biking | cyclist | light skin tone | person cycling</annotation> + <annotation cp="🚴🏼">bicycle | biking | cyclist | medium-light skin tone | person cycling</annotation> + <annotation cp="🚴🏽">bicycle | biking | cyclist | medium skin tone | person cycling</annotation> + <annotation cp="🚴🏾">bicycle | biking | cyclist | medium-dark skin tone | person cycling</annotation> + <annotation cp="🚴🏿">bicycle | biking | cyclist | dark skin tone | person cycling</annotation> + <annotation cp="🚴🏻♂">bicycle | biking | cyclist | light skin tone | man cycling</annotation> + <annotation cp="🚴🏼♂">bicycle | biking | cyclist | man cycling | medium-light skin tone</annotation> + <annotation cp="🚴🏽♂">bicycle | biking | cyclist | man cycling | medium skin tone</annotation> + <annotation cp="🚴🏾♂">bicycle | biking | cyclist | man cycling | medium-dark skin tone</annotation> + <annotation cp="🚴🏿♂">bicycle | biking | cyclist | dark skin tone | man cycling</annotation> + <annotation cp="🚴🏻♀">bicycle | biking | cyclist | light skin tone | woman cycling</annotation> + <annotation cp="🚴🏼♀">bicycle | biking | cyclist | medium-light skin tone | woman cycling</annotation> + <annotation cp="🚴🏽♀">bicycle | biking | cyclist | medium skin tone | woman cycling</annotation> + <annotation cp="🚴🏾♀">bicycle | biking | cyclist | medium-dark skin tone | woman cycling</annotation> + <annotation cp="🚴🏿♀">bicycle | biking | cyclist | dark skin tone | woman cycling</annotation> + <annotation cp="🇺🇲" type="tts">flag: US Outlying Islands</annotation> + <annotation cp="🇻🇮" type="tts">flag: US Virgin Islands</annotation> + </annotations> +</ldml>
diff --git a/third_party/cldr/update.sh b/third_party/cldr/update.sh new file mode 100755 index 0000000..6f4dbf6 --- /dev/null +++ b/third_party/cldr/update.sh
@@ -0,0 +1,31 @@ +#!/bin/bash + +# 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. + +# Run this script to fetch the latest CLDR files from unicode.org. + +# WARNING: This will remove all existing files in //third_party/cldr/src. + +# Currently only fetches files needed for emoji keywords in English. +# If needed, update the unzip line as appropriate. + +# CLDR release to checkout. See http://cldr.unicode.org/index/downloads +CLDR_URL='http://unicode.org/Public/cldr/38.1/cldr-common-38.1.zip' +# To update the CLDR files, change this URL and also update the Version +# field in README.chromium. Then run this script and commit the changes. + +# Set working directory and terminate on error. +set -e +cd "$(dirname "$0")" + +# Download release zip. +curl "$CLDR_URL" -o cldr.zip + +# Remove existing src directory. +rm -rf src + +# Unzip relevant files into src directory and clean zip. +unzip -d src -o cldr.zip common/annotations{Derived,}/{en,en_001}.xml +rm -v cldr.zip \ No newline at end of file
diff --git a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-depth-gpu.html b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-depth-gpu.html index b8af122a..5461c61 100644 --- a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-depth-gpu.html +++ b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-depth-gpu.html
@@ -127,6 +127,10 @@ let options = { requiredFeatures: ['depth-sensing', 'dom-overlay'], domOverlay: { root: textOverlayElement }, + depthSensing: { + usagePreference: ["cpu-optimized"], + dataFormatPreference: ["luminance-alpha"], + } }; navigator.xr.requestSession('immersive-ar', options).then((session) => { @@ -154,6 +158,14 @@ xrRefSpace = refSpace; session.requestAnimationFrame(onXRFrame); }); + + if(session.depthUsage != "cpu-optimized") { + throw new Error("Unsupported depth API usage!"); + } + + if(session.depthDataFormat != "luminance-alpha") { + throw new Error("Unsupported depth data format!"); + } } function onEndSession(session) { @@ -206,6 +218,7 @@ uniformLocations: { depthTexture: gl.getUniformLocation(shaderProgram, 'uDepthTexture'), uvTransform: gl.getUniformLocation(shaderProgram, 'uUvTransform'), + rawValueToMeters: gl.getUniformLocation(shaderProgram, 'uRawValueToMeters'), }, }; @@ -355,6 +368,9 @@ gl.uniformMatrix4fv(programInfo.uniformLocations.uvTransform, false, depthData.normTextureFromNormView.matrix); + gl.uniform1f(programInfo.uniformLocations.rawValueToMeters, + depthData.rawValueToMeters); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); }
diff --git a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-depth.html b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-depth.html index 0f9d3fd..a05c805 100644 --- a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-depth.html +++ b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-depth.html
@@ -128,6 +128,10 @@ let options = { requiredFeatures: ['depth-sensing', 'dom-overlay'], domOverlay: { root: textOverlayElement }, + depthSensing: { + usagePreference: ["cpu-optimized"], + dataFormatPreference: ["luminance-alpha"], + } }; navigator.xr.requestSession('immersive-ar', options).then((session) => { @@ -155,6 +159,14 @@ xrRefSpace = refSpace; session.requestAnimationFrame(onXRFrame); }); + + if(session.depthUsage != "cpu-optimized") { + throw new Error("Unsupported depth API usage!"); + } + + if(session.depthDataFormat != "luminance-alpha") { + throw new Error("Unsupported depth data format!"); + } } function onEndSession(session) { @@ -312,7 +324,7 @@ for(let x = 0; x < depth_width; x = x + RESOLUTION) { for(let y = 0; y < depth_height; y = y + RESOLUTION) { - const distance = depthData.getDepth(x, y); + const distance = depthData.getDepthInMeters(x, y); const depth_coords = vec3.fromValues(x, y, 0);
diff --git a/third_party/webxr_test_pages/webxr-samples/shaders/depth-api-cpu.frag b/third_party/webxr_test_pages/webxr-samples/shaders/depth-api-cpu.frag index 4981f778..58d1fb1 100644 --- a/third_party/webxr_test_pages/webxr-samples/shaders/depth-api-cpu.frag +++ b/third_party/webxr_test_pages/webxr-samples/shaders/depth-api-cpu.frag
@@ -2,7 +2,7 @@ varying float vDepthDistance; -const highp float kMaxDepth = 8.0; // In meters. +const highp float kMaxDepthInMeters = 8.0; // In meters. const float kInvalidDepthThreshold = 0.01; vec3 TurboColormap(in float x); @@ -17,7 +17,7 @@ } void main(void) { - highp float normalized_depth = clamp(vDepthDistance / 8.0, 0.0, 1.0); + highp float normalized_depth = clamp(vDepthDistance / kMaxDepthInMeters, 0.0, 1.0); gl_FragColor = vec4(DepthGetColorVisualization(normalized_depth), 0.75); }
diff --git a/third_party/webxr_test_pages/webxr-samples/shaders/depth-api-gpu.frag b/third_party/webxr_test_pages/webxr-samples/shaders/depth-api-gpu.frag index c2d95e30..3e21fb1 100644 --- a/third_party/webxr_test_pages/webxr-samples/shaders/depth-api-gpu.frag +++ b/third_party/webxr_test_pages/webxr-samples/shaders/depth-api-gpu.frag
@@ -2,17 +2,19 @@ uniform sampler2D uDepthTexture; uniform mat4 uUvTransform; +uniform float uRawValueToMeters; varying vec2 vTexCoord; -float DepthGetMillimeters(in sampler2D depth_texture, in vec2 depth_uv) { +float DepthGetMeters(in sampler2D depth_texture, in vec2 depth_uv) { // Depth is packed into the luminance and alpha components of its texture. - // The texture is a normalized format, storing millimeters. + // The texture is in a normalized format, storing raw values that need to be + // converted to meters. vec2 packedDepthAndVisibility = texture2D(depth_texture, depth_uv).ra; - return dot(packedDepthAndVisibility, vec2(255.0, 256.0 * 255.0)); + return dot(packedDepthAndVisibility, vec2(255.0, 256.0 * 255.0)) * uRawValueToMeters; } -const highp float kMaxDepth = 8000.0; // In millimeters. +const highp float kMaxDepthInMeters = 8.0; const float kInvalidDepthThreshold = 0.01; vec3 TurboColormap(in float x); @@ -30,7 +32,7 @@ vec2 texCoord = (uUvTransform * vec4(vTexCoord.xy, 0, 1)).xy; highp float normalized_depth = clamp( - DepthGetMillimeters(uDepthTexture, texCoord) / kMaxDepth, 0.0, 1.0); + DepthGetMeters(uDepthTexture, texCoord) / kMaxDepthInMeters, 0.0, 1.0); gl_FragColor = vec4(DepthGetColorVisualization(normalized_depth), 0.75); }
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index ccf53ebc..aaf50a8 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -6799,6 +6799,7 @@ <int value="238" label="CSDH_BAD_OWNER"/> <int value="239" label="SYNC_COMPOSITOR_NO_LOCAL_SURFACE_ID"/> <int value="240" label="WCI_INVALID_FULLSCREEN_OPTIONS"/> + <int value="241" label="PAYMENTS_WITHOUT_PERMISSION"/> </enum> <enum name="BadMessageReasonExtensions"> @@ -31008,8 +31009,8 @@ <int value="3682" label="UndeferrableThirdPartySubresourceRequestWithCookie"/> <int value="3683" label="XRDepthSensing"/> <int value="3684" label="XRFrameGetDepthInformation"/> - <int value="3685" label="XRDepthInformationGetDepth"/> - <int value="3686" label="XRDepthInformationDataAttribute"/> + <int value="3685" label="XRCPUDepthInformationGetDepth"/> + <int value="3686" label="XRCPUDepthInformationDataAttribute"/> <int value="3687" label="InterestCohortAPI_interestCohort_Method"/> <int value="3688" label="OBSOLETE_AddressSpaceLocalEmbeddedInPrivateSecureContext"/> @@ -31128,6 +31129,8 @@ <int value="3788" label="RTCPeerConnectionUsingComplexUnifiedPlan"/> <int value="3789" label="WindowScreenIsExtended"/> <int value="3790" label="WindowScreenChange"/> + <int value="3791" label="XRWebGLDepthInformationTextureAttribute"/> + <int value="3792" label="XRWebGLBindingGetDepthInformation"/> </enum> <enum name="FeaturePolicyAllowlistType"> @@ -43285,7 +43288,6 @@ <int value="-1480926949" label="MaterialDesignBookmarks:enabled"/> <int value="-1480866718" label="ash-disable-login-dim-and-blur"/> <int value="-1480606359" label="AssistantIntentPageUrl:enabled"/> - <int value="-1478929417" label="MojoLinuxChannelSharedMem:enabled"/> <int value="-1478876902" label="disable-permission-action-reporting"/> <int value="-1478137998" label="lite-video-default-downlink-bandwidth-kbps"/> <int value="-1477686864" label="OmniboxRichAutocompletion:enabled"/> @@ -44219,6 +44221,7 @@ <int value="-662064703" label="MediaSessionService:enabled"/> <int value="-661978438" label="enable-data-reduction-proxy-lo-fi"/> <int value="-660160292" label="enable-apps-show-on-first-paint"/> + <int value="-658319177" label="VaapiAV1Decoder:disabled"/> <int value="-657808907" label="CopyLinkToText:disabled"/> <int value="-654196854" label="PasswordsKeyboardAccessory:enabled"/> <int value="-653616608" label="MacSyscallSandbox:disabled"/> @@ -45332,6 +45335,7 @@ <int value="415395210" label="TrimOnMemoryPressure:enabled"/> <int value="416116189" label="DeprecateLowUsageCodecs:enabled"/> <int value="416691040" label="SendTabToSelfOmniboxSendingAnimation:disabled"/> + <int value="416760194" label="ExoLockNotification:disabled"/> <int value="416887895" label="enable-password-change-support"/> <int value="417709910" label="AutofillSendExperimentIdsInPaymentsRPCs:disabled"/> @@ -45563,6 +45567,7 @@ <int value="636341169" label="ExploreSites:disabled"/> <int value="636413416" label="OmniboxKeywordSearchButton:disabled"/> <int value="636425179" label="mhtml-generator-option"/> + <int value="636909796" label="VaapiAV1Decoder:enabled"/> <int value="637396292" label="AllBookmarks:enabled"/> <int value="637452937" label="ChromeHomeSurvey:enabled"/> <int value="638845342" @@ -46753,7 +46758,6 @@ label="OmniboxUIExperimentVerticalMarginLimitToNonTouchOnly:disabled"/> <int value="1760946944" label="MacViewsAutofillPopup:disabled"/> <int value="1762320532" label="AutofillKeyboardAccessory:enabled"/> - <int value="1764618580" label="MojoLinuxChannelSharedMem:disabled"/> <int value="1766676896" label="affiliation-based-matching:disabled"/> <int value="1767411597" label="DisallowUnsafeHttpDownloads:enabled"/> <int value="1768759000" label="AutofillProfileServerValidation:disabled"/> @@ -46925,6 +46929,7 @@ <int value="1919917329" label="ImplicitRootScroller:disabled"/> <int value="1920894670" label="OmniboxPreserveDefaultMatchAgainstAsyncUpdate:enabled"/> + <int value="1921543515" label="ExoLockNotification:enabled"/> <int value="1923052799" label="CrostiniUseDlc:disabled"/> <int value="1923496816" label="AssistantIntentTranslateInfo:disabled"/> <int value="1923780021" label="PrivacyReorderedAndroid:enabled"/>
diff --git a/tools/metrics/histograms/histograms_xml/offline/histograms.xml b/tools/metrics/histograms/histograms_xml/offline/histograms.xml index 3bc80ac5..7acaae2 100644 --- a/tools/metrics/histograms/histograms_xml/offline/histograms.xml +++ b/tools/metrics/histograms/histograms_xml/offline/histograms.xml
@@ -81,6 +81,9 @@ <histogram name="OfflineIndicator.ShownDuration" units="ms" expires_after="2021-07-11"> + <obsolete> + Removed M90. Replaced by OfflineIndicator.ShownDurationV2. + </obsolete> <owner>curranmax@chromium.org</owner> <owner>tbansal@chromium.org</owner> <owner>sinansahin@google.com</owner> @@ -91,6 +94,23 @@ </summary> </histogram> +<histogram name="OfflineIndicator.ShownDurationV2" units="ms" + expires_after="2021-08-01"> + <owner>curranmax@chromium.org</owner> + <owner>tbansal@chromium.org</owner> + <summary> + The duration the offline indicator was shown. Recorded when the offline + indicator stops being shown. There are two differences between this + histogram and OfflineIndicator.ShownDuration: 1) the maximum bucket size and + total number of buckets are higher in this histogram, and 2) this histogram + is persisted in perfs. The second point means that if the user backgrounds + then kills Chrome when the Offline Indicator was shown, then we will still + record a sample. Note that in this case, when the user opens Chrome again + and if the Offline Indicator is shown, then it will be treated as a + continuation from before Chrome was killed. + </summary> +</histogram> + <histogram base="true" name="OfflinePages.AccessCount" units="units" expires_after="M85"> <owner>jianli@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 c8781ad0..131e28e 100644 --- a/tools/perf/core/perfetto_binary_roller/binary_deps.json +++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,16 +1,16 @@ { "trace_processor_shell": { "win": { - "hash": "15f9e69440eee271420ffe8cb70f04779163c246", - "remote_path": "perfetto_binaries/trace_processor_shell/win/4825074456917de8843b42e96abea018a7237b75/trace_processor_shell.exe" + "hash": "0c6e959dafcea2ff90282821b1b1afc5805d3c69", + "remote_path": "perfetto_binaries/trace_processor_shell/win/0f2d499389c5de52c1d42310715bf83835e44c48/trace_processor_shell.exe" }, "mac": { "hash": "f2a7837b9050229eaba591e9407581c2295c6314", - "remote_path": "perfetto_binaries/trace_processor_shell/mac/91cc5be54402afc43605c15f07548e53741e0430/trace_processor_shell" + "remote_path": "perfetto_binaries/trace_processor_shell/mac/d8241a7a3e4d6e6a8dc6ec6e00d90ad7a93166eb/trace_processor_shell" }, "linux": { - "hash": "23a050cd7812d362911768fbe6825a7ce145888e", - "remote_path": "perfetto_binaries/trace_processor_shell/linux/91cc5be54402afc43605c15f07548e53741e0430/trace_processor_shell" + "hash": "05d9967eb1f683f8578de30caef481b874e29fa5", + "remote_path": "perfetto_binaries/trace_processor_shell/linux/0f2d499389c5de52c1d42310715bf83835e44c48/trace_processor_shell" } }, "power_profile.sql": {
diff --git a/ui/accessibility/ax_tree_serializer.h b/ui/accessibility/ax_tree_serializer.h index 8158c45..09f0c7b 100644 --- a/ui/accessibility/ax_tree_serializer.h +++ b/ui/accessibility/ax_tree_serializer.h
@@ -646,8 +646,7 @@ base::debug::SetCrashKeyString(reparent_err, error.str().substr(0, 230)); CHECK(false) << error.str(); #endif // defined(AX_FAIL_FAST_BUILD) - // TODO: re-add this, including crash keys above. - // base::debug::DumpWithoutCrashing(); + base::debug::DumpWithoutCrashing(); Reset(); return false; }
diff --git a/ui/base/resource/resource_bundle.cc b/ui/base/resource/resource_bundle.cc index 9f74540..c41afd1c 100644 --- a/ui/base/resource/resource_bundle.cc +++ b/ui/base/resource/resource_bundle.cc
@@ -1067,11 +1067,15 @@ // Fall back on the main data pack (shouldn't be any strings here except // in unittests). data = GetRawDataResource(resource_id); +#if defined(OS_FUCHSIA) + CHECK(!data.empty()); +#else // !defined(OS_FUCHSIA) if (data.empty()) { LOG(WARNING) << "unable to find resource: " << resource_id; NOTREACHED(); return base::string16(); } +#endif // !defined(OS_FUCHSIA) } }
diff --git a/ui/gfx/color_transform.cc b/ui/gfx/color_transform.cc index cac57477..8a072cc 100644 --- a/ui/gfx/color_transform.cc +++ b/ui/gfx/color_transform.cc
@@ -938,8 +938,16 @@ int src_bit_depth, const ColorSpace& dst, int dst_bit_depth) { - steps_.push_back(std::make_unique<ColorTransformMatrix>( - GetRangeAdjustMatrix(src, src_bit_depth))); + // ITU-T H.273: If MatrixCoefficients is equal to 0 (Identity) or 8 (YCgCo), + // range adjustment is performed on R,G,B samples rather than Y,U,V samples. + const bool src_matrix_is_identity_or_ycgco = + src.GetMatrixID() == ColorSpace::MatrixID::GBR || + src.GetMatrixID() == ColorSpace::MatrixID::YCOCG; + auto src_range_adjust_matrix = std::make_unique<ColorTransformMatrix>( + GetRangeAdjustMatrix(src, src_bit_depth)); + + if (!src_matrix_is_identity_or_ycgco) + steps_.push_back(std::move(src_range_adjust_matrix)); if (src.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) { // BT2020 CL is a special case. @@ -949,6 +957,9 @@ std::make_unique<ColorTransformMatrix>(Invert(GetTransferMatrix(src)))); } + if (src_matrix_is_identity_or_ycgco) + steps_.push_back(std::move(src_range_adjust_matrix)); + // If the target color space is not defined, just apply the adjust and // tranfer matrices. This path is used by YUV to RGB color conversion // when full color conversion is not enabled. @@ -1020,6 +1031,17 @@ std::make_unique<ColorTransformFromLinear>(dst.GetTransferID())); } + // ITU-T H.273: If MatrixCoefficients is equal to 0 (Identity) or 8 (YCgCo), + // range adjustment is performed on R,G,B samples rather than Y,U,V samples. + const bool dst_matrix_is_identity_or_ycgco = + dst.GetMatrixID() == ColorSpace::MatrixID::GBR || + dst.GetMatrixID() == ColorSpace::MatrixID::YCOCG; + auto dst_range_adjust_matrix = std::make_unique<ColorTransformMatrix>( + Invert(GetRangeAdjustMatrix(dst, dst_bit_depth))); + + if (dst_matrix_is_identity_or_ycgco) + steps_.push_back(std::move(dst_range_adjust_matrix)); + if (dst.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) { NOTREACHED(); } else { @@ -1027,8 +1049,8 @@ std::make_unique<ColorTransformMatrix>(GetTransferMatrix(dst))); } - steps_.push_back(std::make_unique<ColorTransformMatrix>( - Invert(GetRangeAdjustMatrix(dst, dst_bit_depth)))); + if (!dst_matrix_is_identity_or_ycgco) + steps_.push_back(std::move(dst_range_adjust_matrix)); } ColorTransformInternal::ColorTransformInternal(const ColorSpace& src,
diff --git a/ui/gfx/color_transform_unittest.cc b/ui/gfx/color_transform_unittest.cc index ff78354..46dfa60 100644 --- a/ui/gfx/color_transform_unittest.cc +++ b/ui/gfx/color_transform_unittest.cc
@@ -128,6 +128,42 @@ EXPECT_GT(tmp.z(), tmp.y()); } +TEST(SimpleColorSpace, YCOCGLimitedToSRGB) { + ColorSpace ycocg(ColorSpace::PrimaryID::BT709, + ColorSpace::TransferID::IEC61966_2_1, + ColorSpace::MatrixID::YCOCG, ColorSpace::RangeID::LIMITED); + ColorSpace sRGB = ColorSpace::CreateSRGB(); + std::unique_ptr<ColorTransform> t(ColorTransform::NewColorTransform( + ycocg, sRGB, ColorTransform::Intent::INTENT_ABSOLUTE)); + + ColorTransform::TriStim tmp(16.0f / 255.0f, 0.5f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 0.0f, kMathEpsilon); + + tmp = ColorTransform::TriStim(235.0f / 255.0f, 0.5f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 1.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 1.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 1.0f, kMathEpsilon); + + // Test a blue color + // Use the equations for MatrixCoefficients 8 and VideoFullRangeFlag 0 in + // ITU-T H.273: + // Equations 11-13: E'_R = 0.0, E'_G = 0.0, E'_B = 1.0 + // Equations 20-22: R = 16, G = 16, B = 219 + 16 = 235 + // Equations 44-46: + // Y = Round(0.5 * 16 + 0.25 * (16 + 235)) = Round(70.75) = 71 + // Cb = Round(0.5 * 16 - 0.25 * (16 + 235)) + 128 = Round(-54.75) + 128 = 73 + // Cr = Round(0.5 * (16 - 235)) + 128 = Round(-109.5) + 128 = 18 + tmp = ColorTransform::TriStim(71.0f / 255.0f, 73.0f / 255.0f, 18.0f / 255.0f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 0.0f, 1.0f / 255.0f); + EXPECT_NEAR(tmp.z(), 1.0f, kMathEpsilon); +} + TEST(SimpleColorSpace, TransferFnCancel) { ColorSpace::PrimaryID primary = ColorSpace::PrimaryID::BT709; ColorSpace::MatrixID matrix = ColorSpace::MatrixID::RGB;
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc index 35ef455..8b6064d1 100644 --- a/ui/gfx/render_text.cc +++ b/ui/gfx/render_text.cc
@@ -1190,6 +1190,10 @@ } void RenderText::SetDisplayOffset(int horizontal_offset) { + SetDisplayOffset({horizontal_offset, display_offset_.y()}); +} + +void RenderText::SetDisplayOffset(Vector2d offset) { const int extra_content = GetContentWidth() - display_rect_.width(); const int cursor_width = cursor_enabled_ ? 1 : 0; @@ -1215,24 +1219,30 @@ break; } } - if (horizontal_offset < min_offset) - horizontal_offset = min_offset; - else if (horizontal_offset > max_offset) - horizontal_offset = max_offset; + + const int horizontal_offset = + base::ClampToRange(offset.x(), min_offset, max_offset); + + // y-offset is set only when the vertical alignment is ALIGN_TOP. + // TODO(jongkown.lee): Support other vertical alignments. + DCHECK(vertical_alignment_ == ALIGN_TOP || offset.y() == 0); + const int vertical_offset = base::ClampToRange( + offset.y(), + std::min(display_rect_.height() - GetStringSize().height(), 0), 0); cached_bounds_and_offset_valid_ = true; - display_offset_.set_x(horizontal_offset); + display_offset_ = {horizontal_offset, vertical_offset}; cursor_bounds_ = GetCursorBounds(selection_model_, true); } Vector2d RenderText::GetLineOffset(size_t line_number) { const internal::ShapedText* shaped_text = GetShapedText(); Vector2d offset = display_rect().OffsetFromOrigin(); - // TODO(ckocagil): Apply the display offset for multiline scrolling. if (!multiline()) { offset.Add(GetUpdatedDisplayOffset()); } else { DCHECK_LT(line_number, shaped_text->lines().size()); + offset.Add(GetUpdatedDisplayOffset()); offset.Add( Vector2d(0, shaped_text->lines()[line_number].preceding_heights)); } @@ -2224,9 +2234,8 @@ if (cached_bounds_and_offset_valid_) return; - // TODO(ckocagil): Add support for scrolling multiline text. - int delta_x = 0; + int delta_y = 0; if (cursor_enabled()) { // When cursor is enabled, ensure it is visible. For this, set the valid @@ -2241,9 +2250,16 @@ delta_x = display_rect_.right() - cursor_bounds_.right(); else if (cursor_bounds_.x() < display_rect_.x()) delta_x = display_rect_.x() - cursor_bounds_.x(); + + if (vertical_alignment_ == ALIGN_TOP) { + if (cursor_bounds_.bottom() > display_rect_.bottom()) + delta_y = display_rect_.bottom() - cursor_bounds_.bottom(); + else if (cursor_bounds_.y() < display_rect_.y()) + delta_y = display_rect_.y() - cursor_bounds_.y(); + } } - SetDisplayOffset(display_offset_.x() + delta_x); + SetDisplayOffset(display_offset_ + Vector2d(delta_x, delta_y)); } internal::GraphemeIterator RenderText::GetGraphemeIteratorAtIndex(
diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h index 29c589e..bb7ce1ca82 100644 --- a/ui/gfx/render_text.h +++ b/ui/gfx/render_text.h
@@ -582,6 +582,7 @@ const Vector2d& GetUpdatedDisplayOffset(); void SetDisplayOffset(int horizontal_offset); + void SetDisplayOffset(Vector2d offset); // Returns the line offset from the origin after applying the text alignment // and the display offset.
diff --git a/ui/gfx/render_text_test_api.h b/ui/gfx/render_text_test_api.h index 07c48f4..1de8993 100644 --- a/ui/gfx/render_text_test_api.h +++ b/ui/gfx/render_text_test_api.h
@@ -66,6 +66,10 @@ return render_text_->GetShapedText()->lines(); } + const Vector2d& display_offset() const { + return render_text_->display_offset_; + } + SelectionModel EdgeSelectionModel(VisualCursorDirection direction) { return render_text_->EdgeSelectionModel(direction); }
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc index d18be3d..1b25f80 100644 --- a/ui/gfx/render_text_unittest.cc +++ b/ui/gfx/render_text_unittest.cc
@@ -3332,6 +3332,56 @@ EXPECT_EQ(original_text_direction, render_text->GetDisplayTextDirection()); } +TEST_F(RenderTextTest, MoveCursor_UpDown_Scroll) { + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(100, 30)); + render_text->SetMultiline(true); + render_text->SetVerticalAlignment(ALIGN_TOP); + + const size_t kLineSize = 50; + std::string text; + for (size_t i = 0; i < kLineSize - 1; ++i) + text += "a\n"; + + render_text->SetText(ASCIIToUTF16(text)); + EXPECT_EQ(kLineSize, render_text->GetNumLines()); + + // Move cursor down with scroll. + render_text->SelectRange(Range(0)); + // |line_height| is the distance from the top. + float line_height = + render_text->GetLineSizeF(render_text->selection_model()).height(); + for (size_t i = 1; i < kLineSize; ++i) { + SCOPED_TRACE(base::StringPrintf("Testing line [%" PRIuS "]", i)); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_DOWN, SELECTION_NONE); + ASSERT_EQ(Range(i * 2), render_text->selection()); + ASSERT_TRUE(render_text->display_rect().Contains( + render_text->GetUpdatedCursorBounds())); + line_height += + render_text->GetLineSizeF(render_text->selection_model()).height(); + ASSERT_FLOAT_EQ(test_api()->display_offset().y(), + std::min(0.0f, 30.0f - line_height)); + } + + // Move cursor up with scroll. + // |line_height| is the distance from the bottom. + line_height = + render_text->GetLineSizeF(render_text->selection_model()).height(); + int offset_y = test_api()->display_offset().y(); + for (size_t i = kLineSize - 2; i != size_t{-1}; --i) { + SCOPED_TRACE(base::StringPrintf("Testing line [%" PRIuS "]", i)); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_UP, SELECTION_NONE); + ASSERT_EQ(Range(i * 2), render_text->selection()); + ASSERT_TRUE(render_text->display_rect().Contains( + render_text->GetUpdatedCursorBounds())); + line_height += + render_text->GetLineSizeF(render_text->selection_model()).height(); + ASSERT_FLOAT_EQ(test_api()->display_offset().y(), + offset_y + std::max(0.0f, line_height - 30.0f)); + } + EXPECT_EQ(0, test_api()->display_offset().y()); +} + TEST_F(RenderTextTest, GetDisplayTextDirection) { struct { const char* text;
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn index 00c34dc5..03e80401 100644 --- a/ui/views/BUILD.gn +++ b/ui/views/BUILD.gn
@@ -172,6 +172,7 @@ "controls/table/table_utils.h", "controls/table/table_view.h", "controls/table/table_view_observer.h", + "controls/textarea/textarea.h", "controls/textfield/textfield.h", "controls/textfield/textfield_controller.h", "controls/textfield/textfield_model.h", @@ -379,6 +380,7 @@ "controls/table/table_header.cc", "controls/table/table_utils.cc", "controls/table/table_view.cc", + "controls/textarea/textarea.cc", "controls/textfield/textfield.cc", "controls/textfield/textfield_controller.cc", "controls/textfield/textfield_model.cc", @@ -1128,8 +1130,10 @@ "controls/table/table_view_unittest.cc", "controls/table/test_table_model.cc", "controls/table/test_table_model.h", + "controls/textarea/textarea_unittest.cc", "controls/textfield/textfield_model_unittest.cc", "controls/textfield/textfield_unittest.cc", + "controls/textfield/textfield_unittest.h", "controls/tree/tree_view_unittest.cc", "event_monitor_unittest.cc", "focus/focus_manager_unittest.cc",
diff --git a/ui/views/controls/textarea/textarea.cc b/ui/views/controls/textarea/textarea.cc new file mode 100644 index 0000000..de01539 --- /dev/null +++ b/ui/views/controls/textarea/textarea.cc
@@ -0,0 +1,114 @@ +// Copyright 2019 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 "ui/views/controls/textarea/textarea.h" + +#include "base/logging.h" +#include "ui/base/ime/text_edit_commands.h" +#include "ui/events/event.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/views/metadata/metadata_impl_macros.h" + +namespace views { + +Textarea::Textarea() { + GetRenderText()->SetMultiline(true); + GetRenderText()->SetVerticalAlignment(gfx::ALIGN_TOP); + GetRenderText()->SetWordWrapBehavior(gfx::WRAP_LONG_WORDS); +} + +size_t Textarea::GetNumLines() { + return GetRenderText()->GetNumLines(); +} + +bool Textarea::OnMouseWheel(const ui::MouseWheelEvent& event) { + GetRenderText()->SetDisplayOffset(GetRenderText()->GetUpdatedDisplayOffset() + + gfx::Vector2d(0, event.y_offset())); + UpdateCursorViewPosition(); + SchedulePaint(); + return true; +} + +Textfield::EditCommandResult Textarea::DoExecuteTextEditCommand( + ui::TextEditCommand command) { + bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT; + gfx::VisualCursorDirection begin = rtl ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT; + gfx::VisualCursorDirection end = rtl ? gfx::CURSOR_LEFT : gfx::CURSOR_RIGHT; + + switch (command) { + case ui::TextEditCommand::MOVE_UP: + textfield_model()->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_UP, + gfx::SELECTION_NONE); + break; + case ui::TextEditCommand::MOVE_DOWN: + textfield_model()->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_DOWN, + gfx::SELECTION_NONE); + break; + case ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION: + textfield_model()->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_UP, + gfx::SELECTION_RETAIN); + break; + case ui::TextEditCommand::MOVE_DOWN_AND_MODIFY_SELECTION: + textfield_model()->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_DOWN, + gfx::SELECTION_RETAIN); + break; + case ui::TextEditCommand:: + MOVE_TO_BEGINNING_OF_DOCUMENT_AND_MODIFY_SELECTION: + case ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION: + textfield_model()->MoveCursor(gfx::FIELD_BREAK, begin, + kPageSelectionBehavior); + break; + case ui::TextEditCommand:: + MOVE_TO_BEGINNING_OF_PARAGRAPH_AND_MODIFY_SELECTION: + textfield_model()->MoveCursor(gfx::FIELD_BREAK, begin, + kMoveParagraphSelectionBehavior); + break; + case ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT_AND_MODIFY_SELECTION: + case ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION: + textfield_model()->MoveCursor(gfx::FIELD_BREAK, end, + kPageSelectionBehavior); + break; + case ui::TextEditCommand::MOVE_TO_END_OF_PARAGRAPH_AND_MODIFY_SELECTION: + textfield_model()->MoveCursor(gfx::FIELD_BREAK, end, + kMoveParagraphSelectionBehavior); + break; + default: + return Textfield::DoExecuteTextEditCommand(command); + } + + // TODO(jongkwon.lee): Return |cursor_changed| with actual value. It's okay + // for now because |cursor_changed| is detected afterward in + // |Textfield::ExecuteTextEditCommand|. + return {false, false}; +} + +bool Textarea::PreHandleKeyPressed(const ui::KeyEvent& event) { + if (event.key_code() == ui::VKEY_RETURN) { + DoInsertChar('\n'); + return true; + } + return false; +} + +ui::TextEditCommand Textarea::GetCommandForKeyEvent(const ui::KeyEvent& event) { + if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode()) + return Textfield::GetCommandForKeyEvent(event); + + const bool shift = event.IsShiftDown(); + switch (event.key_code()) { + case ui::VKEY_UP: + return shift ? ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION + : ui::TextEditCommand::MOVE_UP; + case ui::VKEY_DOWN: + return shift ? ui::TextEditCommand::MOVE_DOWN_AND_MODIFY_SELECTION + : ui::TextEditCommand::MOVE_DOWN; + default: + return Textfield::GetCommandForKeyEvent(event); + } +} + +BEGIN_METADATA(Textarea, Textfield) +END_METADATA + +} // namespace views
diff --git a/ui/views/controls/textarea/textarea.h b/ui/views/controls/textarea/textarea.h new file mode 100644 index 0000000..9e0699c --- /dev/null +++ b/ui/views/controls/textarea/textarea.h
@@ -0,0 +1,36 @@ +// Copyright 2019 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 UI_VIEWS_CONTROLS_TEXTAREA_TEXTAREA_H_ +#define UI_VIEWS_CONTROLS_TEXTAREA_TEXTAREA_H_ + +#include "ui/views/controls/textfield/textfield.h" + +namespace views { + +// A multiline textfield implementation. +class VIEWS_EXPORT Textarea : public Textfield { + public: + METADATA_HEADER(Textarea); + + Textarea(); + ~Textarea() override = default; + + // Returns the number of lines of the text. + size_t GetNumLines(); + + // Textfield: + bool OnMouseWheel(const ui::MouseWheelEvent& event) override; + + protected: + // Textfield: + Textfield::EditCommandResult DoExecuteTextEditCommand( + ui::TextEditCommand command) override; + bool PreHandleKeyPressed(const ui::KeyEvent& event) override; + ui::TextEditCommand GetCommandForKeyEvent(const ui::KeyEvent& event) override; +}; + +} // namespace views + +#endif // UI_VIEWS_CONTROLS_TEXTAREA_TEXTAREA_H_
diff --git a/ui/views/controls/textarea/textarea_unittest.cc b/ui/views/controls/textarea/textarea_unittest.cc new file mode 100644 index 0000000..a75ccf7c3 --- /dev/null +++ b/ui/views/controls/textarea/textarea_unittest.cc
@@ -0,0 +1,299 @@ +// Copyright 2019 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 "ui/views/controls/textarea/textarea.h" + +#include <memory> +#include <string> +#include <vector> + +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "ui/events/event.h" +#include "ui/gfx/render_text.h" +#include "ui/strings/grit/ui_strings.h" +#include "ui/views/controls/textfield/textfield_test_api.h" +#include "ui/views/controls/textfield/textfield_unittest.h" +#include "ui/views/style/platform_style.h" +#include "ui/views/widget/widget.h" + +namespace { + +const base::char16 kHebrewLetterSamekh = 0x05E1; + +} // namespace + +namespace views { +namespace { + +class TextareaTest : public test::TextfieldTest { + public: + TextareaTest() = default; + ~TextareaTest() override = default; + + // TextfieldTest: + void SetUp() override { + TextfieldTest::SetUp(); + + ASSERT_FALSE(textarea_); + textarea_ = PrepareTextfields(0, std::make_unique<Textarea>(), + gfx::Rect(100, 100, 800, 100)); + } + + protected: + void RunMoveUpDownTest(int start_index, + ui::KeyboardCode key_code, + std::vector<int> expected) { + DCHECK(key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN); + textarea_->SetSelectedRange(gfx::Range(start_index)); + for (size_t i = 0; i < expected.size(); ++i) { + SCOPED_TRACE(testing::Message() + << (key_code == ui::VKEY_UP ? "MOVE UP " : "MOVE DOWN ") + << i + 1 << " times from Range " << start_index); + SendKeyEvent(key_code); + EXPECT_EQ(gfx::Range(expected[i]), textarea_->GetSelectedRange()); + } + } + + size_t GetCursorLine() const { + return test_api_->GetRenderText()->GetLineContainingCaret( + textarea_->GetSelectionModel()); + } + + // TextfieldTest: + void SendHomeEvent(bool shift) override { + SendKeyEvent(ui::VKEY_HOME, shift, TestingNativeMac()); + } + + // TextfieldTest: + void SendEndEvent(bool shift) override { + SendKeyEvent(ui::VKEY_END, shift, TestingNativeMac()); + } + + Textarea* textarea_ = nullptr; + + private: + DISALLOW_COPY_AND_ASSIGN(TextareaTest); +}; + +} // namespace + +// Disabled when using XKB for crbug.com/1171828. +#if BUILDFLAG(USE_XKBCOMMON) +#define MAYBE_InsertNewlineTest DISABLED_InsertNewlineTest +#else +#define MAYBE_InsertNewlineTest InsertNewlineTest +#endif // BUILDFLAG(USE_XKBCOMMON) +TEST_F(TextareaTest, MAYBE_InsertNewlineTest) { + for (size_t i = 0; i < 5; i++) { + SendKeyEvent(static_cast<ui::KeyboardCode>(ui::VKEY_A + i)); + SendKeyEvent(ui::VKEY_RETURN); + } + EXPECT_STR_EQ("a\nb\nc\nd\ne\n", textarea_->GetText()); +} + +TEST_F(TextareaTest, PasteNewlineTest) { + const std::string& kText = "abc\n \n"; + textarea_->SetText(base::ASCIIToUTF16(kText)); + textarea_->SelectAll(false); + textarea_->ExecuteCommand(Textfield::kCopy, 0); + textarea_->SetText(base::string16()); + textarea_->ExecuteCommand(Textfield::kPaste, 0); + EXPECT_STR_EQ(kText, textarea_->GetText()); +} + +// Re-enable when crbug.com/1163587 is fixed. +TEST_F(TextareaTest, DISABLED_CursorMovement) { + textarea_->SetText(base::ASCIIToUTF16("one\n\ntwo three")); + + // Move Up/Down at the front of the line. + RunMoveUpDownTest(0, ui::VKEY_DOWN, {4, 5, 14}); + RunMoveUpDownTest(5, ui::VKEY_UP, {4, 0, 0}); + + // Move Up/Down at the end of the line. + RunMoveUpDownTest(3, ui::VKEY_DOWN, {4, 8, 14}); + RunMoveUpDownTest(14, ui::VKEY_UP, {4, 3, 0}); + + // Move Up/Down at the middle position. + RunMoveUpDownTest(2, ui::VKEY_DOWN, {4, 7, 14}); + RunMoveUpDownTest(7, ui::VKEY_UP, {4, 2, 0}); + + // Test Home/End key on each lines. + textarea_->SetSelectedRange(gfx::Range(2)); // First line. + SendHomeEvent(false); + EXPECT_EQ(gfx::Range(0), textarea_->GetSelectedRange()); + SendEndEvent(false); + EXPECT_EQ(gfx::Range(3), textarea_->GetSelectedRange()); + textarea_->SetSelectedRange(gfx::Range(4)); // 2nd line. + SendHomeEvent(false); + EXPECT_EQ(gfx::Range(4), textarea_->GetSelectedRange()); + SendEndEvent(false); + EXPECT_EQ(gfx::Range(4), textarea_->GetSelectedRange()); + textarea_->SetSelectedRange(gfx::Range(7)); // 3rd line. + SendHomeEvent(false); + EXPECT_EQ(gfx::Range(5), textarea_->GetSelectedRange()); + SendEndEvent(false); + EXPECT_EQ(gfx::Range(14), textarea_->GetSelectedRange()); +} + +// Ensure cursor view is always inside display rect. +TEST_F(TextareaTest, CursorViewBounds) { + textarea_->SetBounds(0, 0, 100, 31); + for (size_t i = 0; i < 10; ++i) { + SCOPED_TRACE(base::StringPrintf("VKEY_RETURN %" PRIuS " times", i + 1)); + SendKeyEvent(ui::VKEY_RETURN); + ASSERT_TRUE(textarea_->GetVisibleBounds().Contains(GetCursorViewRect())); + ASSERT_FALSE(GetCursorViewRect().size().IsEmpty()); + } + + for (size_t i = 0; i < 10; ++i) { + SCOPED_TRACE(base::StringPrintf("VKEY_UP %" PRIuS " times", i + 1)); + SendKeyEvent(ui::VKEY_UP); + ASSERT_TRUE(textarea_->GetVisibleBounds().Contains(GetCursorViewRect())); + ASSERT_FALSE(GetCursorViewRect().size().IsEmpty()); + } +} + +TEST_F(TextareaTest, LineSelection) { + textarea_->SetText(base::ASCIIToUTF16("12\n34567 89")); + + // Place the cursor after "5". + textarea_->SetEditableSelectionRange(gfx::Range(6)); + + // Select line towards right. + SendEndEvent(true); + EXPECT_STR_EQ("67 89", textarea_->GetSelectedText()); + + // Select line towards left. On Mac, the existing selection should be extended + // to cover the whole line. + SendHomeEvent(true); + + if (Textarea::kLineSelectionBehavior == gfx::SELECTION_EXTEND) + EXPECT_STR_EQ("34567 89", textarea_->GetSelectedText()); + else + EXPECT_STR_EQ("345", textarea_->GetSelectedText()); + + EXPECT_TRUE(textarea_->GetSelectedRange().is_reversed()); + + // Select line towards right. + SendEndEvent(true); + + if (Textarea::kLineSelectionBehavior == gfx::SELECTION_EXTEND) + EXPECT_STR_EQ("34567 89", textarea_->GetSelectedText()); + else + EXPECT_STR_EQ("67 89", textarea_->GetSelectedText()); + + EXPECT_FALSE(textarea_->GetSelectedRange().is_reversed()); +} + +// Disabled on Mac for crbug.com/1171826. +#if defined(OS_MAC) +#define MAYBE_MoveUpDownAndModifySelection DISABLED_MoveUpDownAndModifySelection +#else +#define MAYBE_MoveUpDownAndModifySelection MoveUpDownAndModifySelection +#endif // defined(OS_MAC) +TEST_F(TextareaTest, MAYBE_MoveUpDownAndModifySelection) { + textarea_->SetText(base::ASCIIToUTF16("12\n34567 89")); + textarea_->SetEditableSelectionRange(gfx::Range(6)); + EXPECT_EQ(1U, GetCursorLine()); + + // Up key should place the cursor after "2" not after newline to place the + // cursor on the first line. + SendKeyEvent(ui::VKEY_UP); + EXPECT_EQ(0U, GetCursorLine()); + EXPECT_EQ(gfx::Range(2), textarea_->GetSelectedRange()); + + // Down key after Up key should select the same range as the previous one. + SendKeyEvent(ui::VKEY_DOWN); + EXPECT_EQ(1U, GetCursorLine()); + EXPECT_EQ(gfx::Range(6), textarea_->GetSelectedRange()); + + // Shift+Up should select the text to the upper line position including + // the newline character. + SendKeyEvent(ui::VKEY_UP, true /* shift */, false /* command */); + EXPECT_EQ(gfx::Range(6, 2), textarea_->GetSelectedRange()); + + // Shift+Down should collapse the selection. + SendKeyEvent(ui::VKEY_DOWN, true /* shift */, false /* command */); + EXPECT_EQ(gfx::Range(6), textarea_->GetSelectedRange()); + + // Shift+Down again should select the text to the end of the last line. + SendKeyEvent(ui::VKEY_DOWN, true /* shift */, false /* command */); + EXPECT_EQ(gfx::Range(6, 11), textarea_->GetSelectedRange()); +} + +TEST_F(TextareaTest, MovePageUpDownAndModifySelection) { + textarea_->SetText(base::ASCIIToUTF16("12\n34567 89")); + textarea_->SetEditableSelectionRange(gfx::Range(6)); + + EXPECT_TRUE( + textarea_->IsTextEditCommandEnabled(ui::TextEditCommand::MOVE_PAGE_UP)); + EXPECT_TRUE( + textarea_->IsTextEditCommandEnabled(ui::TextEditCommand::MOVE_PAGE_DOWN)); + EXPECT_TRUE(textarea_->IsTextEditCommandEnabled( + ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION)); + EXPECT_TRUE(textarea_->IsTextEditCommandEnabled( + ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION)); + + test_api_->ExecuteTextEditCommand(ui::TextEditCommand::MOVE_PAGE_UP); + EXPECT_EQ(gfx::Range(0), textarea_->GetSelectedRange()); + + test_api_->ExecuteTextEditCommand(ui::TextEditCommand::MOVE_PAGE_DOWN); + EXPECT_EQ(gfx::Range(11), textarea_->GetSelectedRange()); + + textarea_->SetEditableSelectionRange(gfx::Range(6)); + test_api_->ExecuteTextEditCommand( + ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION); + EXPECT_EQ(gfx::Range(6, 0), textarea_->GetSelectedRange()); + + test_api_->ExecuteTextEditCommand( + ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION); + + if (Textarea::kLineSelectionBehavior == gfx::SELECTION_EXTEND) + EXPECT_EQ(gfx::Range(0, 11), textarea_->GetSelectedRange()); + else + EXPECT_EQ(gfx::Range(6, 11), textarea_->GetSelectedRange()); +} + +// Ensure the textarea breaks the long word and scrolls on overflow. +TEST_F(TextareaTest, OverflowTest) { + const size_t count = 50U; + textarea_->SetBounds(0, 0, 60, 40); + + textarea_->SetText(base::string16(count, 'a')); + EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds())); + + textarea_->SetText(base::string16(count, kHebrewLetterSamekh)); + EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds())); +} + +TEST_F(TextareaTest, OverflowInRTLTest) { + const size_t count = 50U; + textarea_->SetBounds(0, 0, 60, 40); + std::string locale = base::i18n::GetConfiguredLocale(); + base::i18n::SetICUDefaultLocale("he"); + + textarea_->SetText(base::string16(count, 'a')); + EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds())); + + textarea_->SetText(base::string16(count, kHebrewLetterSamekh)); + EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds())); + + // Reset locale. + base::i18n::SetICUDefaultLocale(locale); +} + +TEST_F(TextareaTest, OnBlurTest) { + const std::string& kText = "abcdef"; + textarea_->SetText(base::ASCIIToUTF16(kText)); + + SendEndEvent(false); + EXPECT_EQ(kText.size(), textarea_->GetCursorPosition()); + + // A focus loss should not change the cursor position. + textarea_->OnBlur(); + EXPECT_EQ(kText.size(), textarea_->GetCursorPosition()); +} + +} // namespace views
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc index 76d8083..c714fec 100644 --- a/ui/views/controls/textfield/textfield.cc +++ b/ui/views/controls/textfield/textfield.cc
@@ -118,159 +118,6 @@ kTextfieldSelectedRange, }; -#if defined(OS_APPLE) -constexpr gfx::SelectionBehavior kLineSelectionBehavior = gfx::SELECTION_EXTEND; -constexpr gfx::SelectionBehavior kWordSelectionBehavior = gfx::SELECTION_CARET; -constexpr gfx::SelectionBehavior kMoveParagraphSelectionBehavior = - gfx::SELECTION_CARET; -#else -constexpr gfx::SelectionBehavior kLineSelectionBehavior = gfx::SELECTION_RETAIN; -constexpr gfx::SelectionBehavior kWordSelectionBehavior = gfx::SELECTION_RETAIN; -constexpr gfx::SelectionBehavior kMoveParagraphSelectionBehavior = - gfx::SELECTION_RETAIN; -#endif - -// Get the default command for a given key |event|. -ui::TextEditCommand GetCommandForKeyEvent(const ui::KeyEvent& event) { - if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode()) - return ui::TextEditCommand::INVALID_COMMAND; - - const bool shift = event.IsShiftDown(); -#if defined(OS_APPLE) - const bool command = event.IsCommandDown(); -#endif - const bool control = event.IsControlDown() || event.IsCommandDown(); - const bool alt = event.IsAltDown() || event.IsAltGrDown(); - switch (event.key_code()) { - case ui::VKEY_Z: - if (control && !shift && !alt) - return ui::TextEditCommand::UNDO; - return (control && shift && !alt) ? ui::TextEditCommand::REDO - : ui::TextEditCommand::INVALID_COMMAND; - case ui::VKEY_Y: - return (control && !alt) ? ui::TextEditCommand::REDO - : ui::TextEditCommand::INVALID_COMMAND; - case ui::VKEY_A: - return (control && !alt) ? ui::TextEditCommand::SELECT_ALL - : ui::TextEditCommand::INVALID_COMMAND; - case ui::VKEY_X: - return (control && !alt) ? ui::TextEditCommand::CUT - : ui::TextEditCommand::INVALID_COMMAND; - case ui::VKEY_C: - return (control && !alt) ? ui::TextEditCommand::COPY - : ui::TextEditCommand::INVALID_COMMAND; - case ui::VKEY_V: - return (control && !alt) ? ui::TextEditCommand::PASTE - : ui::TextEditCommand::INVALID_COMMAND; - case ui::VKEY_RIGHT: - // Ignore alt+right, which may be a browser navigation shortcut. - if (alt) - return ui::TextEditCommand::INVALID_COMMAND; - if (!shift) { - return control ? ui::TextEditCommand::MOVE_WORD_RIGHT - : ui::TextEditCommand::MOVE_RIGHT; - } - return control ? ui::TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION - : ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION; - case ui::VKEY_LEFT: - // Ignore alt+left, which may be a browser navigation shortcut. - if (alt) - return ui::TextEditCommand::INVALID_COMMAND; - if (!shift) { - return control ? ui::TextEditCommand::MOVE_WORD_LEFT - : ui::TextEditCommand::MOVE_LEFT; - } - return control ? ui::TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION - : ui::TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION; - case ui::VKEY_HOME: - if (shift) { - return ui::TextEditCommand:: - MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION; - } -#if defined(OS_APPLE) - return ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT; -#else - return ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE; -#endif - case ui::VKEY_END: - if (shift) - return ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION; -#if defined(OS_APPLE) - return ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT; -#else - return ui::TextEditCommand::MOVE_TO_END_OF_LINE; -#endif - case ui::VKEY_UP: -#if defined(OS_APPLE) - if (control && shift) { - return ui::TextEditCommand:: - MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION; - } - if (command) - return ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT; - return shift ? ui::TextEditCommand:: - MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION - : ui::TextEditCommand::MOVE_UP; -#else - return shift ? ui::TextEditCommand:: - MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION - : ui::TextEditCommand::INVALID_COMMAND; -#endif - case ui::VKEY_DOWN: -#if defined(OS_APPLE) - if (control && shift) { - return ui::TextEditCommand::MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION; - } - if (command) - return ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT; - return shift - ? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION - : ui::TextEditCommand::MOVE_DOWN; -#else - return shift - ? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION - : ui::TextEditCommand::INVALID_COMMAND; -#endif - case ui::VKEY_BACK: - if (!control) { -#if defined(OS_WIN) - if (alt) - return shift ? ui::TextEditCommand::REDO : ui::TextEditCommand::UNDO; -#endif - return ui::TextEditCommand::DELETE_BACKWARD; - } -#if defined(OS_LINUX) || defined(OS_CHROMEOS) - // Only erase by line break on Linux and ChromeOS. - if (shift) - return ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE; -#endif - return ui::TextEditCommand::DELETE_WORD_BACKWARD; - case ui::VKEY_DELETE: -#if defined(OS_LINUX) || defined(OS_CHROMEOS) - // Only erase by line break on Linux and ChromeOS. - if (shift && control) - return ui::TextEditCommand::DELETE_TO_END_OF_LINE; -#endif - if (control) - return ui::TextEditCommand::DELETE_WORD_FORWARD; - return shift ? ui::TextEditCommand::CUT - : ui::TextEditCommand::DELETE_FORWARD; - case ui::VKEY_INSERT: - if (control && !shift) - return ui::TextEditCommand::COPY; - return (shift && !control) ? ui::TextEditCommand::PASTE - : ui::TextEditCommand::INVALID_COMMAND; - case ui::VKEY_PRIOR: - return control ? ui::TextEditCommand::SCROLL_PAGE_UP - : ui::TextEditCommand::INVALID_COMMAND; - case ui::VKEY_NEXT: - return control ? ui::TextEditCommand::SCROLL_PAGE_DOWN - : ui::TextEditCommand::INVALID_COMMAND; - default: - return ui::TextEditCommand::INVALID_COMMAND; - } -} - // Returns the ui::TextEditCommand corresponding to the |command_id| menu // action. |has_selection| is true if the textfield has an active selection. // Keep in sync with UpdateContextMenu. @@ -749,9 +596,15 @@ // beyond their legibility, or enlarging controls dynamically with content. gfx::Rect bounds = GetLocalBounds(); const gfx::Insets insets = GetInsets(); - // The text will draw with the correct vertical alignment if we don't apply - // the vertical insets. - bounds.Inset(insets.left(), 0, insets.right(), 0); + + if (GetRenderText()->multiline()) { + bounds.Inset(insets); + } else { + // The text will draw with the correct vertical alignment if we don't apply + // the vertical insets. + bounds.Inset(insets.left(), 0, insets.right(), 0); + } + bounds.set_x(GetMirroredXForRect(bounds)); GetRenderText()->SetDisplayRect(bounds); UpdateAfterChange(TextChangeType::kNone, true); @@ -853,6 +706,9 @@ } bool Textfield::OnKeyPressed(const ui::KeyEvent& event) { + if (PreHandleKeyPressed(event)) + return true; + ui::TextEditCommand edit_command = scheduled_text_edit_command_; scheduled_text_edit_command_ = ui::TextEditCommand::INVALID_COMMAND; @@ -1241,7 +1097,8 @@ render_text->set_focused(false); // If necessary, yank the cursor to the logical start of the textfield. - if (PlatformStyle::kTextfieldScrollsToStartOnFocusChange) + if (PlatformStyle::kTextfieldScrollsToStartOnFocusChange && + !render_text->multiline()) model_->MoveCursorTo(gfx::SelectionModel(0, gfx::CURSOR_FORWARD)); if (GetInputMethod()) { @@ -1871,7 +1728,7 @@ #if defined(OS_APPLE) return true; #else - return false; + return GetRenderText()->multiline(); #endif case ui::TextEditCommand::INSERT_TEXT: case ui::TextEditCommand::SET_MARK: @@ -2006,6 +1863,30 @@ void Textfield::ExecuteTextEditCommand(ui::TextEditCommand command) { DestroyTouchSelection(); + // We only execute the commands enabled in Textfield::IsTextEditCommandEnabled + // below. Hence don't do a virtual IsTextEditCommandEnabled call. + if (!IsTextEditCommandEnabled(command)) + return; + + OnBeforeUserAction(); + + gfx::SelectionModel selection_model = GetSelectionModel(); + bool text_changed, cursor_changed; + std::tie(text_changed, cursor_changed) = DoExecuteTextEditCommand(command); + + cursor_changed |= (GetSelectionModel() != selection_model); + if (cursor_changed && HasSelection()) + UpdateSelectionClipboard(); + UpdateAfterChange( + text_changed ? TextChangeType::kUserTriggered : TextChangeType::kNone, + cursor_changed); + OnAfterUserAction(); +} + +Textfield::EditCommandResult Textfield::DoExecuteTextEditCommand( + ui::TextEditCommand command) { + bool changed = false; + bool cursor_changed = false; bool add_to_kill_buffer = false; base::AutoReset<bool> show_rejection_ui(&show_rejection_ui_if_any_, true); @@ -2028,48 +1909,40 @@ break; } - // We only execute the commands enabled in Textfield::IsTextEditCommandEnabled - // below. Hence don't do a virtual IsTextEditCommandEnabled call. - if (!IsTextEditCommandEnabled(command)) - return; - - bool changed = false; bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT; gfx::VisualCursorDirection begin = rtl ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT; gfx::VisualCursorDirection end = rtl ? gfx::CURSOR_LEFT : gfx::CURSOR_RIGHT; - gfx::SelectionModel selection_model = GetSelectionModel(); - OnBeforeUserAction(); switch (command) { case ui::TextEditCommand::DELETE_BACKWARD: - changed = model_->Backspace(add_to_kill_buffer); + changed = cursor_changed = model_->Backspace(add_to_kill_buffer); break; case ui::TextEditCommand::DELETE_FORWARD: - changed = model_->Delete(add_to_kill_buffer); + changed = cursor_changed = model_->Delete(add_to_kill_buffer); break; case ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE: model_->MoveCursor(gfx::LINE_BREAK, begin, gfx::SELECTION_RETAIN); - changed = model_->Backspace(add_to_kill_buffer); + changed = cursor_changed = model_->Backspace(add_to_kill_buffer); break; case ui::TextEditCommand::DELETE_TO_BEGINNING_OF_PARAGRAPH: model_->MoveCursor(gfx::FIELD_BREAK, begin, gfx::SELECTION_RETAIN); - changed = model_->Backspace(add_to_kill_buffer); + changed = cursor_changed = model_->Backspace(add_to_kill_buffer); break; case ui::TextEditCommand::DELETE_TO_END_OF_LINE: model_->MoveCursor(gfx::LINE_BREAK, end, gfx::SELECTION_RETAIN); - changed = model_->Delete(add_to_kill_buffer); + changed = cursor_changed = model_->Delete(add_to_kill_buffer); break; case ui::TextEditCommand::DELETE_TO_END_OF_PARAGRAPH: model_->MoveCursor(gfx::FIELD_BREAK, end, gfx::SELECTION_RETAIN); - changed = model_->Delete(add_to_kill_buffer); + changed = cursor_changed = model_->Delete(add_to_kill_buffer); break; case ui::TextEditCommand::DELETE_WORD_BACKWARD: model_->MoveCursor(gfx::WORD_BREAK, begin, gfx::SELECTION_RETAIN); - changed = model_->Backspace(add_to_kill_buffer); + changed = cursor_changed = model_->Backspace(add_to_kill_buffer); break; case ui::TextEditCommand::DELETE_WORD_FORWARD: model_->MoveCursor(gfx::WORD_BREAK, end, gfx::SELECTION_RETAIN); - changed = model_->Delete(add_to_kill_buffer); + changed = cursor_changed = model_->Delete(add_to_kill_buffer); break; case ui::TextEditCommand::MOVE_BACKWARD: model_->MoveCursor(gfx::CHARACTER_BREAK, begin, gfx::SELECTION_NONE); @@ -2182,28 +2055,28 @@ kWordSelectionBehavior); break; case ui::TextEditCommand::UNDO: - changed = model_->Undo(); + changed = cursor_changed = model_->Undo(); break; case ui::TextEditCommand::REDO: - changed = model_->Redo(); + changed = cursor_changed = model_->Redo(); break; case ui::TextEditCommand::CUT: - changed = Cut(); + changed = cursor_changed = Cut(); break; case ui::TextEditCommand::COPY: Copy(); break; case ui::TextEditCommand::PASTE: - changed = Paste(); + changed = cursor_changed = Paste(); break; case ui::TextEditCommand::SELECT_ALL: SelectAll(false); break; case ui::TextEditCommand::TRANSPOSE: - changed = model_->Transpose(); + changed = cursor_changed = model_->Transpose(); break; case ui::TextEditCommand::YANK: - changed = model_->Yank(); + changed = cursor_changed = model_->Yank(); break; case ui::TextEditCommand::INSERT_TEXT: case ui::TextEditCommand::SET_MARK: @@ -2213,14 +2086,7 @@ break; } - const auto text_change_type = - changed ? TextChangeType::kUserTriggered : TextChangeType::kNone; - const bool cursor_changed = - changed || (GetSelectionModel() != selection_model); - if (cursor_changed && HasSelection()) - UpdateSelectionClipboard(); - UpdateAfterChange(text_change_type, cursor_changed); - OnAfterUserAction(); + return {changed, cursor_changed}; } void Textfield::OffsetDoubleClickWord(int offset) { @@ -2276,6 +2142,151 @@ std::move(callback)); } +bool Textfield::PreHandleKeyPressed(const ui::KeyEvent& event) { + return false; +} + +ui::TextEditCommand Textfield::GetCommandForKeyEvent( + const ui::KeyEvent& event) { + if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode()) + return ui::TextEditCommand::INVALID_COMMAND; + + const bool shift = event.IsShiftDown(); +#if defined(OS_APPLE) + const bool command = event.IsCommandDown(); +#endif + const bool control = event.IsControlDown() || event.IsCommandDown(); + const bool alt = event.IsAltDown() || event.IsAltGrDown(); + switch (event.key_code()) { + case ui::VKEY_Z: + if (control && !shift && !alt) + return ui::TextEditCommand::UNDO; + return (control && shift && !alt) ? ui::TextEditCommand::REDO + : ui::TextEditCommand::INVALID_COMMAND; + case ui::VKEY_Y: + return (control && !alt) ? ui::TextEditCommand::REDO + : ui::TextEditCommand::INVALID_COMMAND; + case ui::VKEY_A: + return (control && !alt) ? ui::TextEditCommand::SELECT_ALL + : ui::TextEditCommand::INVALID_COMMAND; + case ui::VKEY_X: + return (control && !alt) ? ui::TextEditCommand::CUT + : ui::TextEditCommand::INVALID_COMMAND; + case ui::VKEY_C: + return (control && !alt) ? ui::TextEditCommand::COPY + : ui::TextEditCommand::INVALID_COMMAND; + case ui::VKEY_V: + return (control && !alt) ? ui::TextEditCommand::PASTE + : ui::TextEditCommand::INVALID_COMMAND; + case ui::VKEY_RIGHT: + // Ignore alt+right, which may be a browser navigation shortcut. + if (alt) + return ui::TextEditCommand::INVALID_COMMAND; + if (!shift) { + return control ? ui::TextEditCommand::MOVE_WORD_RIGHT + : ui::TextEditCommand::MOVE_RIGHT; + } + return control ? ui::TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION + : ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION; + case ui::VKEY_LEFT: + // Ignore alt+left, which may be a browser navigation shortcut. + if (alt) + return ui::TextEditCommand::INVALID_COMMAND; + if (!shift) { + return control ? ui::TextEditCommand::MOVE_WORD_LEFT + : ui::TextEditCommand::MOVE_LEFT; + } + return control ? ui::TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION + : ui::TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION; + case ui::VKEY_HOME: + if (shift) { + return ui::TextEditCommand:: + MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION; + } +#if defined(OS_APPLE) + return ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT; +#else + return ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE; +#endif + case ui::VKEY_END: + if (shift) + return ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION; +#if defined(OS_APPLE) + return ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT; +#else + return ui::TextEditCommand::MOVE_TO_END_OF_LINE; +#endif + case ui::VKEY_UP: +#if defined(OS_APPLE) + if (control && shift) { + return ui::TextEditCommand:: + MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION; + } + if (command) + return ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT; + return shift ? ui::TextEditCommand:: + MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION + : ui::TextEditCommand::MOVE_UP; +#else + return shift ? ui::TextEditCommand:: + MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION + : ui::TextEditCommand::INVALID_COMMAND; +#endif + case ui::VKEY_DOWN: +#if defined(OS_APPLE) + if (control && shift) { + return ui::TextEditCommand::MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION; + } + if (command) + return ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT; + return shift + ? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION + : ui::TextEditCommand::MOVE_DOWN; +#else + return shift + ? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION + : ui::TextEditCommand::INVALID_COMMAND; +#endif + case ui::VKEY_BACK: + if (!control) { +#if defined(OS_WIN) + if (alt) + return shift ? ui::TextEditCommand::REDO : ui::TextEditCommand::UNDO; +#endif + return ui::TextEditCommand::DELETE_BACKWARD; + } +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + // Only erase by line break on Linux and ChromeOS. + if (shift) + return ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE; +#endif + return ui::TextEditCommand::DELETE_WORD_BACKWARD; + case ui::VKEY_DELETE: +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + // Only erase by line break on Linux and ChromeOS. + if (shift && control) + return ui::TextEditCommand::DELETE_TO_END_OF_LINE; +#endif + if (control) + return ui::TextEditCommand::DELETE_WORD_FORWARD; + return shift ? ui::TextEditCommand::CUT + : ui::TextEditCommand::DELETE_FORWARD; + case ui::VKEY_INSERT: + if (control && !shift) + return ui::TextEditCommand::COPY; + return (shift && !control) ? ui::TextEditCommand::PASTE + : ui::TextEditCommand::INVALID_COMMAND; + case ui::VKEY_PRIOR: + return control ? ui::TextEditCommand::SCROLL_PAGE_UP + : ui::TextEditCommand::INVALID_COMMAND; + case ui::VKEY_NEXT: + return control ? ui::TextEditCommand::SCROLL_PAGE_DOWN + : ui::TextEditCommand::INVALID_COMMAND; + default: + return ui::TextEditCommand::INVALID_COMMAND; + } +} + //////////////////////////////////////////////////////////////////////////////// // Textfield, private:
diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h index c3be0b0..ed8bc19f 100644 --- a/ui/views/controls/textfield/textfield.h +++ b/ui/views/controls/textfield/textfield.h
@@ -11,6 +11,7 @@ #include <memory> #include <set> #include <string> +#include <utility> #if defined(OS_WIN) #include <vector> @@ -85,6 +86,29 @@ kLastCommandId = kSelectAll, }; +#if defined(OS_APPLE) + static constexpr gfx::SelectionBehavior kLineSelectionBehavior = + gfx::SELECTION_EXTEND; + static constexpr gfx::SelectionBehavior kWordSelectionBehavior = + gfx::SELECTION_CARET; + static constexpr gfx::SelectionBehavior kMoveParagraphSelectionBehavior = + gfx::SELECTION_CARET; + static constexpr gfx::SelectionBehavior kPageSelectionBehavior = + gfx::SELECTION_EXTEND; +#else + static constexpr gfx::SelectionBehavior kLineSelectionBehavior = + gfx::SELECTION_RETAIN; + static constexpr gfx::SelectionBehavior kWordSelectionBehavior = + gfx::SELECTION_RETAIN; + static constexpr gfx::SelectionBehavior kMoveParagraphSelectionBehavior = + gfx::SELECTION_RETAIN; + static constexpr gfx::SelectionBehavior kPageSelectionBehavior = + gfx::SELECTION_RETAIN; +#endif + + // Pair of |text_changed|, |cursor_changed|. + using EditCommandResult = std::pair<bool, bool>; + // Returns the text cursor blink time, or 0 for no blinking. static base::TimeDelta GetCaretBlinkInterval(); @@ -437,6 +461,8 @@ views::PropertyChangedCallback callback) WARN_UNUSED_RESULT; protected: + TextfieldModel* textfield_model() { return model_.get(); } + // Inserts or appends a character in response to an IME operation. virtual void DoInsertChar(base::char16 ch); @@ -474,6 +500,20 @@ // gesture event. void RequestFocusForGesture(const ui::GestureEventDetails& details); + virtual Textfield::EditCommandResult DoExecuteTextEditCommand( + ui::TextEditCommand command); + + // Handles key press event ahead of OnKeyPressed(). This is used for Textarea + // to handle the return key. Use TextfieldController::HandleKeyEvent to + // intercept the key event in other cases. + virtual bool PreHandleKeyPressed(const ui::KeyEvent& event); + + // Get the default command for a given key |event|. + virtual ui::TextEditCommand GetCommandForKeyEvent(const ui::KeyEvent& event); + + // Update the cursor position in the text field. + void UpdateCursorViewPosition(); + private: friend class TextfieldTestApi; @@ -527,9 +567,6 @@ // A callback function to periodically update the cursor node_data. void UpdateCursorVisibility(); - // Update the cursor position in the text field. - void UpdateCursorViewPosition(); - // Gets the style::TextStyle that should be used. int GetTextStyle() const;
diff --git a/ui/views/controls/textfield/textfield_model.cc b/ui/views/controls/textfield/textfield_model.cc index 82e865e..ba182dd 100644 --- a/ui/views/controls/textfield/textfield_model.cc +++ b/ui/views/controls/textfield/textfield_model.cc
@@ -626,6 +626,11 @@ if (text.empty()) return false; + if (render_text()->multiline()) { + InsertTextInternal(text, false); + return true; + } + // Leading/trailing whitespace is often selected accidentally, and is rarely // critical to include (e.g. when pasting into a find bar). Trim it. By // contrast, whitespace in the middle of the string may need exact
diff --git a/ui/views/controls/textfield/textfield_model.h b/ui/views/controls/textfield/textfield_model.h index 5050309..7770089 100644 --- a/ui/views/controls/textfield/textfield_model.h +++ b/ui/views/controls/textfield/textfield_model.h
@@ -40,6 +40,7 @@ namespace test { class BridgedNativeWidgetTest; +class TextfieldTest; } // namespace test // A model that represents text content for a views::Textfield. @@ -270,7 +271,7 @@ friend class internal::Edit; friend class test::BridgedNativeWidgetTest; friend class TextfieldModelTest; - friend class TextfieldTest; + friend class test::TextfieldTest; FRIEND_TEST_ALL_PREFIXES(TextfieldModelTest, UndoRedo_BasicTest); FRIEND_TEST_ALL_PREFIXES(TextfieldModelTest, UndoRedo_CutCopyPasteTest);
diff --git a/ui/views/controls/textfield/textfield_unittest.cc b/ui/views/controls/textfield/textfield_unittest.cc index f8923f5..8867572 100644 --- a/ui/views/controls/textfield/textfield_unittest.cc +++ b/ui/views/controls/textfield/textfield_unittest.cc
@@ -2,30 +2,29 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ui/views/controls/textfield/textfield.h" +#include "ui/views/controls/textfield/textfield_unittest.h" #include <stddef.h> #include <stdint.h> #include <set> #include <string> -#include <utility> #include <vector> #include "base/command_line.h" -#include "base/feature_list.h" #include "base/format_macros.h" #include "base/i18n/rtl.h" #include "base/pickle.h" #include "base/stl_util.h" #include "base/strings/string16.h" +#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_node_data.h" -#include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" +#include "ui/base/clipboard/test/test_clipboard.h" #include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/emoji/emoji_panel_helper.h" #include "ui/base/ime/constants.h" @@ -41,20 +40,17 @@ #include "ui/events/event_processor.h" #include "ui/events/event_utils.h" #include "ui/events/keycodes/dom/dom_code.h" -#include "ui/events/keycodes/keyboard_codes.h" #include "ui/events/test/event_generator.h" #include "ui/events/test/keyboard_layout.h" #include "ui/gfx/render_text.h" #include "ui/gfx/render_text_test_api.h" #include "ui/strings/grit/ui_strings.h" -#include "ui/views/controls/textfield/textfield_controller.h" #include "ui/views/controls/textfield/textfield_model.h" #include "ui/views/controls/textfield/textfield_test_api.h" #include "ui/views/focus/focus_manager.h" #include "ui/views/style/platform_style.h" #include "ui/views/test/ax_event_counter.h" #include "ui/views/test/test_views_delegate.h" -#include "ui/views/test/views_test_base.h" #include "ui/views/test/widget_test.h" #include "ui/views/views_features.h" #include "ui/views/widget/widget.h" @@ -85,18 +81,79 @@ using base::UTF8ToUTF16; using base::WideToUTF16; -#define EXPECT_STR_EQ(ascii, utf16) EXPECT_EQ(ASCIIToUTF16(ascii), utf16) +namespace views { +namespace test { -namespace { +const ui::EventType kFocusEvent = + base::FeatureList::IsEnabled(features::kTextfieldFocusOnTapUp) + ? ui::ET_GESTURE_TAP + : ui::ET_GESTURE_TAP_DOWN; const base::char16 kHebrewLetterSamekh = 0x05E1; +// Convenience to make constructing a GestureEvent simpler. +class GestureEventForTest : public ui::GestureEvent { + public: + GestureEventForTest(int x, int y, ui::GestureEventDetails details) + : GestureEvent(x, y, ui::EF_NONE, base::TimeTicks(), details) {} + + private: + DISALLOW_COPY_AND_ASSIGN(GestureEventForTest); +}; + +// This controller will happily destroy the target field passed on +// construction when a key event is triggered. +class TextfieldDestroyerController : public TextfieldController { + public: + explicit TextfieldDestroyerController(Textfield* target) : target_(target) { + target_->set_controller(this); + } + + Textfield* target() { return target_.get(); } + + // TextfieldController: + bool HandleKeyEvent(Textfield* sender, + const ui::KeyEvent& key_event) override { + if (target_) + target_->OnBlur(); + target_.reset(); + return false; + } + + private: + std::unique_ptr<Textfield> target_; + + DISALLOW_COPY_AND_ASSIGN(TextfieldDestroyerController); +}; + +// Class that focuses a textfield when it sees a KeyDown event. +class TextfieldFocuser : public View { + public: + explicit TextfieldFocuser(Textfield* textfield) : textfield_(textfield) { + SetFocusBehavior(FocusBehavior::ALWAYS); + } + + void set_consume(bool consume) { consume_ = consume; } + + // View: + bool OnKeyPressed(const ui::KeyEvent& event) override { + textfield_->RequestFocus(); + return consume_; + } + + private: + bool consume_ = true; + Textfield* textfield_; + + DISALLOW_COPY_AND_ASSIGN(TextfieldFocuser); +}; + class MockInputMethod : public ui::InputMethodBase { public: MockInputMethod(); ~MockInputMethod() override; - // Overridden from InputMethod: + // InputMethod: ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* key) override; void OnTextInputTypeChanged(const ui::TextInputClient* client) override; void OnCaretBoundsChanged(const ui::TextInputClient* client) override {} @@ -120,7 +177,6 @@ void SetCompositionTextForNextKey(const ui::CompositionText& composition); void SetResultTextForNextKey(const base::string16& result); - int count_show_virtual_keyboard_ = 0; private: // Overridden from InputMethodBase. @@ -150,6 +206,8 @@ bool text_input_type_changed_ = false; bool cancel_composition_called_ = false; + int count_show_virtual_keyboard_ = 0; + DISALLOW_COPY_AND_ASSIGN(MockInputMethod); }; @@ -273,8 +331,9 @@ class TestTextfield : public views::Textfield { public: TestTextfield() = default; + ~TestTextfield() override = default; - // ui::TextInputClient overrides: + // ui::TextInputClient: void InsertChar(const ui::KeyEvent& e) override { views::Textfield::InsertChar(e); #if defined(OS_APPLE) @@ -303,7 +362,7 @@ } private: - // views::View override: + // views::View: void OnKeyEvent(ui::KeyEvent* event) override { key_received_ = true; event_flags_ = event->flags(); @@ -333,256 +392,198 @@ DISALLOW_COPY_AND_ASSIGN(TestTextfield); }; -// Convenience to make constructing a GestureEvent simpler. -class GestureEventForTest : public ui::GestureEvent { - public: - GestureEventForTest(int x, int y, ui::GestureEventDetails details) - : GestureEvent(x, y, 0, base::TimeTicks(), details) {} +TextfieldTest::TextfieldTest() { + input_method_ = new MockInputMethod(); + ui::SetUpInputMethodForTesting(input_method_); +} - private: - DISALLOW_COPY_AND_ASSIGN(GestureEventForTest); -}; +TextfieldTest::~TextfieldTest() = default; -// This controller will happily destroy the target textfield passed on -// construction when a key event is triggered. -class TextfieldDestroyerController : public views::TextfieldController { - public: - explicit TextfieldDestroyerController(views::Textfield* target) - : target_(target) { - target_->set_controller(this); - } +void TextfieldTest::SetUp() { + // OS clipboard is a global resource, which causes flakiness when unit tests + // run in parallel. So, use a per-instance test clipboard. + ui::Clipboard::SetClipboardForCurrentThread( + std::make_unique<ui::TestClipboard>()); + ViewsTestBase::SetUp(); +} - views::Textfield* target() { return target_.get(); } +void TextfieldTest::TearDown() { + if (widget_) + widget_->Close(); + // Clear kill buffer used for "Yank" text editing command so that no state + // persists between tests. + TextfieldModel::ClearKillBuffer(); + ViewsTestBase::TearDown(); +} - // views::TextfieldController: - bool HandleKeyEvent(views::Textfield* sender, - const ui::KeyEvent& key_event) override { - if (target_) - target_->OnBlur(); - target_.reset(); - return false; - } +ui::ClipboardBuffer TextfieldTest::GetAndResetCopiedToClipboard() { + return std::exchange(copied_to_clipboard_, ui::ClipboardBuffer::kMaxValue); +} - private: - std::unique_ptr<views::Textfield> target_; -}; - -// Class that focuses a textfield when it sees a KeyDown event. -class TextfieldFocuser : public views::View { - public: - explicit TextfieldFocuser(views::Textfield* textfield) - : textfield_(textfield) { - SetFocusBehavior(FocusBehavior::ALWAYS); - } - - void set_consume(bool consume) { consume_ = consume; } - - // View: - bool OnKeyPressed(const ui::KeyEvent& event) override { - textfield_->RequestFocus(); - return consume_; - } - - private: - bool consume_ = true; - views::Textfield* textfield_; - - DISALLOW_COPY_AND_ASSIGN(TextfieldFocuser); -}; - -base::string16 GetClipboardText(ui::ClipboardBuffer clipboard_buffer) { +base::string16 TextfieldTest::GetClipboardText( + ui::ClipboardBuffer clipboard_buffer) { base::string16 text; ui::Clipboard::GetForCurrentThread()->ReadText( clipboard_buffer, /* data_dst = */ nullptr, &text); return text; } -void SetClipboardText(ui::ClipboardBuffer clipboard_buffer, - const std::string& text) { +void TextfieldTest::SetClipboardText(ui::ClipboardBuffer clipboard_buffer, + const std::string& text) { ui::ScopedClipboardWriter(clipboard_buffer).WriteText(ASCIIToUTF16(text)); } -} // namespace +void TextfieldTest::ContentsChanged(Textfield* sender, + const base::string16& new_contents) { + // Paste calls TextfieldController::ContentsChanged() explicitly even if the + // paste action did not change the content. So |new_contents| may match + // |last_contents_|. For more info, see http://crbug.com/79002 + last_contents_ = new_contents; +} -namespace views { +void TextfieldTest::OnBeforeUserAction(Textfield* sender) { + ++on_before_user_action_; +} -class TextfieldTest : public ViewsTestBase, public TextfieldController { - public: - TextfieldTest() { - input_method_ = new MockInputMethod(); - ui::SetUpInputMethodForTesting(input_method_); +void TextfieldTest::OnAfterUserAction(Textfield* sender) { + ++on_after_user_action_; +} + +void TextfieldTest::OnAfterCutOrCopy(ui::ClipboardBuffer clipboard_type) { + copied_to_clipboard_ = clipboard_type; +} + +void TextfieldTest::InitTextfield(int count) { + ASSERT_FALSE(textfield_); + textfield_ = PrepareTextfields(count, std::make_unique<TestTextfield>(), + gfx::Rect(100, 100, 100, 100)); +} + +void TextfieldTest::PrepareTextfieldsInternal(int count, + Textfield* textfield, + View* container, + gfx::Rect bounds) { + input_method_->SetDelegate( + test::WidgetTest::GetInputMethodDelegateForWidget(widget_.get())); + + textfield->set_controller(this); + textfield->SetBoundsRect(bounds); + textfield->SetID(1); + test_api_ = std::make_unique<TextfieldTestApi>(textfield); + + for (int i = 1; i < count; ++i) { + Textfield* textfield = + container->AddChildView(std::make_unique<Textfield>()); + textfield->SetID(i + 1); } - // ::testing::Test: - void TearDown() override { - if (widget_) - widget_->Close(); - // Clear kill buffer used for "Yank" text editing command so that no state - // persists between tests. - TextfieldModel::ClearKillBuffer(); - ViewsTestBase::TearDown(); - } + model_ = test_api_->model(); + model_->ClearEditHistory(); - ui::ClipboardBuffer GetAndResetCopiedToClipboard() { - ui::ClipboardBuffer clipboard_buffer = copied_to_clipboard_; - copied_to_clipboard_ = ui::ClipboardBuffer::kMaxValue; - return clipboard_buffer; - } + // Since the window type is activatable, showing the widget will also + // activate it. Calling Activate directly is insufficient, since that does + // not also _focus_ an aura::Window (i.e. using the FocusClient). Both the + // widget and the textfield must have focus to properly handle input. + widget_->Show(); + textfield->RequestFocus(); - // TextfieldController: - void ContentsChanged(Textfield* sender, - const base::string16& new_contents) override { - // Paste calls TextfieldController::ContentsChanged() explicitly even if the - // paste action did not change the content. So |new_contents| may match - // |last_contents_|. For more info, see http://crbug.com/79002 - last_contents_ = new_contents; - } + event_generator_ = + std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_.get())); + event_generator_->set_target(ui::test::EventGenerator::Target::WINDOW); + event_target_ = textfield; +} - void OnBeforeUserAction(Textfield* sender) override { - ++on_before_user_action_; - } +ui::MenuModel* TextfieldTest::GetContextMenuModel() { + test_api_->UpdateContextMenu(); + return test_api_->context_menu_contents(); +} - void OnAfterUserAction(Textfield* sender) override { - ++on_after_user_action_; - } - - void OnAfterCutOrCopy(ui::ClipboardBuffer clipboard_buffer) override { - copied_to_clipboard_ = clipboard_buffer; - } - - void InitTextfield() { InitTextfields(1); } - - void InitTextfields(int count) { - ASSERT_FALSE(textfield_); - textfield_ = new TestTextfield(); - textfield_->set_controller(this); - widget_ = new Widget(); - - // The widget type must be an activatable type, and we don't want to worry - // about the non-client view, which leaves just TYPE_WINDOW_FRAMELESS. - Widget::InitParams params = - CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); - - params.bounds = gfx::Rect(100, 100, 100, 100); - widget_->Init(std::move(params)); - input_method_->SetDelegate( - test::WidgetTest::GetInputMethodDelegateForWidget(widget_)); - View* container = widget_->SetContentsView(std::make_unique<View>()); - container->AddChildView(textfield_); - textfield_->SetBoundsRect(params.bounds); - textfield_->SetID(1); - test_api_ = std::make_unique<TextfieldTestApi>(textfield_); - - for (int i = 1; i < count; i++) { - Textfield* textfield = new Textfield(); - container->AddChildView(textfield); - textfield->SetID(i + 1); - } - - model_ = test_api_->model(); - model_->ClearEditHistory(); - - // Since the window type is activatable, showing the widget will also - // activate it. Calling Activate directly is insufficient, since that does - // not also _focus_ an aura::Window (i.e. using the FocusClient). Both the - // widget and the textfield must have focus to properly handle input. - widget_->Show(); - textfield_->RequestFocus(); - - event_generator_ = - std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_)); - event_generator_->set_target(ui::test::EventGenerator::Target::WINDOW); - } - ui::MenuModel* GetContextMenuModel() { - test_api_->UpdateContextMenu(); - return test_api_->context_menu_contents(); - } - - // True if native Mac keystrokes should be used (to avoid ifdef litter). - bool TestingNativeMac() { +bool TextfieldTest::TestingNativeMac() const { #if defined(OS_APPLE) - return true; + return true; #else - return false; + return false; #endif - } +} - bool TestingNativeCrOs() const { +bool TextfieldTest::TestingNativeCrOs() const { #if BUILDFLAG(IS_CHROMEOS_ASH) - return true; + return true; #else - return false; + return false; #endif // BUILDFLAG(IS_CHROMEOS_ASH) - } +} - protected: - void SendKeyPress(ui::KeyboardCode key_code, int flags) { - event_generator_->PressKey(key_code, flags); - } +void TextfieldTest::SendKeyPress(ui::KeyboardCode key_code, int flags) { + event_generator_->PressKey(key_code, flags); +} - void SendKeyEvent(ui::KeyboardCode key_code, - bool alt, - bool shift, - bool control_or_command, - bool caps_lock) { - bool control = control_or_command; - bool command = false; +void TextfieldTest::SendKeyEvent(ui::KeyboardCode key_code, + bool alt, + bool shift, + bool control_or_command, + bool caps_lock) { + bool control = control_or_command; + bool command = false; - // By default, swap control and command for native events on Mac. This - // handles most cases. - if (TestingNativeMac()) - std::swap(control, command); + // By default, swap control and command for native events on Mac. This + // handles most cases. + if (TestingNativeMac()) + std::swap(control, command); - int flags = - (shift ? ui::EF_SHIFT_DOWN : 0) | (control ? ui::EF_CONTROL_DOWN : 0) | - (alt ? ui::EF_ALT_DOWN : 0) | (command ? ui::EF_COMMAND_DOWN : 0) | - (caps_lock ? ui::EF_CAPS_LOCK_ON : 0); + int flags = + (shift ? ui::EF_SHIFT_DOWN : 0) | (control ? ui::EF_CONTROL_DOWN : 0) | + (alt ? ui::EF_ALT_DOWN : 0) | (command ? ui::EF_COMMAND_DOWN : 0) | + (caps_lock ? ui::EF_CAPS_LOCK_ON : 0); - SendKeyPress(key_code, flags); - } + SendKeyPress(key_code, flags); +} - void SendKeyEvent(ui::KeyboardCode key_code, - bool shift, - bool control_or_command) { - SendKeyEvent(key_code, false, shift, control_or_command, false); - } +void TextfieldTest::SendKeyEvent(ui::KeyboardCode key_code, + bool shift, + bool control_or_command) { + SendKeyEvent(key_code, false, shift, control_or_command, false); +} - void SendKeyEvent(ui::KeyboardCode key_code) { - SendKeyEvent(key_code, false, false); - } +void TextfieldTest::SendKeyEvent(ui::KeyboardCode key_code) { + SendKeyEvent(key_code, false, false); +} - void SendKeyEvent(base::char16 ch) { SendKeyEvent(ch, ui::EF_NONE, false); } +void TextfieldTest::SendKeyEvent(base::char16 ch) { + SendKeyEvent(ch, ui::EF_NONE, false); +} - void SendKeyEvent(base::char16 ch, int flags) { - SendKeyEvent(ch, flags, false); - } +void TextfieldTest::SendKeyEvent(base::char16 ch, int flags) { + SendKeyEvent(ch, flags, false); +} - void SendKeyEvent(base::char16 ch, int flags, bool from_vk) { - if (ch < 0x80) { - ui::KeyboardCode code = - ch == ' ' ? ui::VKEY_SPACE - : static_cast<ui::KeyboardCode>(ui::VKEY_A + ch - 'a'); - SendKeyPress(code, flags); - } else { - // For unicode characters, assume they come from IME rather than the - // keyboard. So they are dispatched directly to the input method. But on - // Mac, key events don't pass through InputMethod. Hence they are - // dispatched regularly. - ui::KeyEvent event(ch, ui::VKEY_UNKNOWN, ui::DomCode::NONE, flags); - if (from_vk) { - ui::Event::Properties properties; - properties[ui::kPropertyFromVK] = - std::vector<uint8_t>(ui::kPropertyFromVKSize); - event.SetProperties(properties); - } -#if defined(OS_APPLE) - event_generator_->Dispatch(&event); -#else - input_method_->DispatchKeyEvent(&event); -#endif +void TextfieldTest::SendKeyEvent(base::char16 ch, int flags, bool from_vk) { + if (ch < 0x80) { + ui::KeyboardCode code = + ch == ' ' ? ui::VKEY_SPACE + : static_cast<ui::KeyboardCode>(ui::VKEY_A + ch - 'a'); + SendKeyPress(code, flags); + } else { + // For unicode characters, assume they come from IME rather than the + // keyboard. So they are dispatched directly to the input method. But on + // Mac, key events don't pass through InputMethod. Hence they are + // dispatched regularly. + ui::KeyEvent event(ch, ui::VKEY_UNKNOWN, ui::DomCode::NONE, flags); + if (from_vk) { + ui::Event::Properties properties; + properties[ui::kPropertyFromVK] = + std::vector<uint8_t>(ui::kPropertyFromVKSize); + event.SetProperties(properties); } +#if defined(OS_APPLE) + event_generator_->Dispatch(&event); +#else + input_method_->DispatchKeyEvent(&event); +#endif } +} +void TextfieldTest::DispatchMockInputMethodKeyEvent() { // Send a key to trigger MockInputMethod::DispatchKeyEvent(). Note the // specific VKEY isn't used (MockInputMethod will mock a ui::VKEY_PROCESSKEY // whenever it has a test composition). However, on Mac, it can't be a letter @@ -590,244 +591,220 @@ // and don't have a meaningful ui::KeyEvent that would trigger // DispatchKeyEvent(). It also can't be VKEY_ENTER, since those key events may // need to be suppressed when interacting with real system IME. - void DispatchMockInputMethodKeyEvent() { SendKeyEvent(ui::VKEY_INSERT); } + SendKeyEvent(ui::VKEY_INSERT); +} - // Sends a platform-specific move (and select) to the logical start of line. - // Eg. this should move (and select) to the right end of line for RTL text. - void SendHomeEvent(bool shift) { - if (TestingNativeMac()) { - // [NSResponder moveToBeginningOfLine:] is the correct way to do this on - // Mac, but that doesn't have a default key binding. Since - // views::Textfield doesn't currently support multiple lines, the same - // effect can be achieved by Cmd+Up which maps to - // [NSResponder moveToBeginningOfDocument:]. - SendKeyEvent(ui::VKEY_UP, shift /* shift */, true /* command */); - return; - } - SendKeyEvent(ui::VKEY_HOME, shift /* shift */, false /* control */); +// Sends a platform-specific move (and select) to the logical start of line. +// Eg. this should move (and select) to the right end of line for RTL text. +void TextfieldTest::SendHomeEvent(bool shift) { + if (TestingNativeMac()) { + // [NSResponder moveToBeginningOfLine:] is the correct way to do this on + // Mac, but that doesn't have a default key binding. Since + // views::Textfield doesn't currently support multiple lines, the same + // effect can be achieved by Cmd+Up which maps to + // [NSResponder moveToBeginningOfDocument:]. + SendKeyEvent(ui::VKEY_UP, shift /* shift */, true /* command */); + return; } + SendKeyEvent(ui::VKEY_HOME, shift /* shift */, false /* control */); +} - // Sends a platform-specific move (and select) to the logical end of line. - void SendEndEvent(bool shift) { - if (TestingNativeMac()) { - SendKeyEvent(ui::VKEY_DOWN, shift, true); // Cmd+Down. - return; - } - SendKeyEvent(ui::VKEY_END, shift, false); +// Sends a platform-specific move (and select) to the logical end of line. +void TextfieldTest::SendEndEvent(bool shift) { + if (TestingNativeMac()) { + SendKeyEvent(ui::VKEY_DOWN, shift, true); // Cmd+Down. + return; } + SendKeyEvent(ui::VKEY_END, shift, false); +} - // Sends {delete, move, select} word {forward, backward}. - void SendWordEvent(ui::KeyboardCode key, bool shift) { - bool alt = false; - bool control = true; - bool caps = false; - if (TestingNativeMac()) { - // Use Alt+Left/Right/Backspace on native Mac. - alt = true; - control = false; - } - SendKeyEvent(key, alt, shift, control, caps); +// Sends {delete, move, select} word {forward, backward}. +void TextfieldTest::SendWordEvent(ui::KeyboardCode key, bool shift) { + bool alt = false; + bool control = true; + bool caps = false; + if (TestingNativeMac()) { + // Use Alt+Left/Right/Backspace on native Mac. + alt = true; + control = false; } + SendKeyEvent(key, alt, shift, control, caps); +} - // Sends Shift+Delete if supported, otherwise Cmd+X again. - void SendAlternateCut() { - if (TestingNativeMac()) - SendKeyEvent(ui::VKEY_X, false, true); - else - SendKeyEvent(ui::VKEY_DELETE, true, false); - } +// Sends Shift+Delete if supported, otherwise Cmd+X again. +void TextfieldTest::SendAlternateCut() { + if (TestingNativeMac()) + SendKeyEvent(ui::VKEY_X, false, true); + else + SendKeyEvent(ui::VKEY_DELETE, true, false); +} - // Sends Ctrl+Insert if supported, otherwise Cmd+C again. - void SendAlternateCopy() { - if (TestingNativeMac()) - SendKeyEvent(ui::VKEY_C, false, true); - else - SendKeyEvent(ui::VKEY_INSERT, false, true); - } +// Sends Ctrl+Insert if supported, otherwise Cmd+C again. +void TextfieldTest::SendAlternateCopy() { + if (TestingNativeMac()) + SendKeyEvent(ui::VKEY_C, false, true); + else + SendKeyEvent(ui::VKEY_INSERT, false, true); +} - // Sends Shift+Insert if supported, otherwise Cmd+V again. - void SendAlternatePaste() { - if (TestingNativeMac()) - SendKeyEvent(ui::VKEY_V, false, true); - else - SendKeyEvent(ui::VKEY_INSERT, true, false); - } +// Sends Shift+Insert if supported, otherwise Cmd+V again. +void TextfieldTest::SendAlternatePaste() { + if (TestingNativeMac()) + SendKeyEvent(ui::VKEY_V, false, true); + else + SendKeyEvent(ui::VKEY_INSERT, true, false); +} - View* GetFocusedView() { - return widget_->GetFocusManager()->GetFocusedView(); - } +View* TextfieldTest::GetFocusedView() { + return widget_->GetFocusManager()->GetFocusedView(); +} - int GetCursorPositionX(int cursor_pos) { - return test_api_->GetRenderText() - ->GetCursorBounds(gfx::SelectionModel(cursor_pos, gfx::CURSOR_FORWARD), - false) - .x(); - } +int TextfieldTest::GetCursorPositionX(int cursor_pos) { + return test_api_->GetRenderText() + ->GetCursorBounds(gfx::SelectionModel(cursor_pos, gfx::CURSOR_FORWARD), + false) + .x(); +} - int GetCursorYForTesting() { - return test_api_->GetRenderText()->GetLineOffset(0).y() + 1; - } +int TextfieldTest::GetCursorYForTesting() { + return test_api_->GetRenderText()->GetLineOffset(0).y() + 1; +} - // Get the current cursor bounds. - gfx::Rect GetCursorBounds() { - return test_api_->GetRenderText()->GetUpdatedCursorBounds(); - } +gfx::Rect TextfieldTest::GetCursorBounds() { + return test_api_->GetRenderText()->GetUpdatedCursorBounds(); +} - // Get the cursor bounds of |sel|. - gfx::Rect GetCursorBounds(const gfx::SelectionModel& sel) { - return test_api_->GetRenderText()->GetCursorBounds(sel, true); - } +// Gets the cursor bounds of |sel|. +gfx::Rect TextfieldTest::GetCursorBounds(const gfx::SelectionModel& sel) { + return test_api_->GetRenderText()->GetCursorBounds(sel, true); +} - gfx::Rect GetDisplayRect() { - return test_api_->GetRenderText()->display_rect(); - } +gfx::Rect TextfieldTest::GetDisplayRect() { + return test_api_->GetRenderText()->display_rect(); +} - // Mouse click on the point whose x-axis is |bound|'s x plus |x_offset| and - // y-axis is in the middle of |bound|'s vertical range. - void MouseClick(const gfx::Rect bound, int x_offset) { - gfx::Point point(bound.x() + x_offset, bound.y() + bound.height() / 2); - ui::MouseEvent click(ui::ET_MOUSE_PRESSED, point, point, +gfx::Rect TextfieldTest::GetCursorViewRect() { + return test_api_->GetCursorViewRect(); +} + +// Performs a mouse click on the point whose x-axis is |bound|'s x plus +// |x_offset| and y-axis is in the middle of |bound|'s vertical range. +void TextfieldTest::MouseClick(const gfx::Rect bound, int x_offset) { + gfx::Point point(bound.x() + x_offset, bound.y() + bound.height() / 2); + ui::MouseEvent click(ui::ET_MOUSE_PRESSED, point, point, + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON); + event_target_->OnMousePressed(click); + ui::MouseEvent release(ui::ET_MOUSE_RELEASED, point, point, ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON); - textfield_->OnMousePressed(click); - ui::MouseEvent release(ui::ET_MOUSE_RELEASED, point, point, - ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, - ui::EF_LEFT_MOUSE_BUTTON); - textfield_->OnMouseReleased(release); - } + event_target_->OnMouseReleased(release); +} - // This is to avoid double/triple click. - void NonClientMouseClick() { - ui::MouseEvent click(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), +// This is to avoid double/triple click. +void TextfieldTest::NonClientMouseClick() { + ui::MouseEvent click(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), + ui::EventTimeForNow(), + ui::EF_LEFT_MOUSE_BUTTON | ui::EF_IS_NON_CLIENT, + ui::EF_LEFT_MOUSE_BUTTON); + event_target_->OnMousePressed(click); + ui::MouseEvent release(ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON | ui::EF_IS_NON_CLIENT, ui::EF_LEFT_MOUSE_BUTTON); - textfield_->OnMousePressed(click); - ui::MouseEvent release(ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(), - ui::EventTimeForNow(), - ui::EF_LEFT_MOUSE_BUTTON | ui::EF_IS_NON_CLIENT, - ui::EF_LEFT_MOUSE_BUTTON); - textfield_->OnMouseReleased(release); - } + event_target_->OnMouseReleased(release); +} - void VerifyTextfieldContextMenuContents(bool textfield_has_selection, - bool can_undo, - ui::MenuModel* menu) { - const auto& text = textfield_->GetText(); - const bool is_all_selected = - !text.empty() && - textfield_->GetSelectedRange().length() == text.length(); +void TextfieldTest::VerifyTextfieldContextMenuContents( + bool textfield_has_selection, + bool can_undo, + ui::MenuModel* menu) { + const auto& text = textfield_->GetText(); + const bool is_all_selected = + !text.empty() && textfield_->GetSelectedRange().length() == text.length(); - int menu_index = 0; + int menu_index = 0; #if defined(OS_APPLE) - if (textfield_has_selection) { - EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Look Up "Selection" */)); - EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); - } + if (textfield_has_selection) { + EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Look Up "Selection" */)); + EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); + } #endif - if (ui::IsEmojiPanelSupported()) { - EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* EMOJI */)); - EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); - } - - EXPECT_EQ(can_undo, menu->IsEnabledAt(menu_index++ /* UNDO */)); + if (ui::IsEmojiPanelSupported()) { + EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* EMOJI */)); EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); - EXPECT_EQ(textfield_has_selection, - menu->IsEnabledAt(menu_index++ /* CUT */)); - EXPECT_EQ(textfield_has_selection, - menu->IsEnabledAt(menu_index++ /* COPY */)); - EXPECT_NE(GetClipboardText(ui::ClipboardBuffer::kCopyPaste).empty(), - menu->IsEnabledAt(menu_index++ /* PASTE */)); - EXPECT_EQ(textfield_has_selection, - menu->IsEnabledAt(menu_index++ /* DELETE */)); - EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); - EXPECT_EQ(!is_all_selected, - menu->IsEnabledAt(menu_index++ /* SELECT ALL */)); } - void PressMouseButton(ui::EventFlags mouse_button_flags) { - ui::MouseEvent press(ui::ET_MOUSE_PRESSED, mouse_position_, mouse_position_, - ui::EventTimeForNow(), mouse_button_flags, - mouse_button_flags); - textfield_->OnMousePressed(press); - } + EXPECT_EQ(can_undo, menu->IsEnabledAt(menu_index++ /* UNDO */)); + EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); + EXPECT_EQ(textfield_has_selection, menu->IsEnabledAt(menu_index++ /* CUT */)); + EXPECT_EQ(textfield_has_selection, + menu->IsEnabledAt(menu_index++ /* COPY */)); + EXPECT_NE(GetClipboardText(ui::ClipboardBuffer::kCopyPaste).empty(), + menu->IsEnabledAt(menu_index++ /* PASTE */)); + EXPECT_EQ(textfield_has_selection, + menu->IsEnabledAt(menu_index++ /* DELETE */)); + EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); + EXPECT_EQ(!is_all_selected, menu->IsEnabledAt(menu_index++ /* SELECT ALL */)); +} - void ReleaseMouseButton(ui::EventFlags mouse_button_flags) { - ui::MouseEvent release(ui::ET_MOUSE_RELEASED, mouse_position_, - mouse_position_, ui::EventTimeForNow(), - mouse_button_flags, mouse_button_flags); - textfield_->OnMouseReleased(release); - } +void TextfieldTest::PressMouseButton(ui::EventFlags mouse_button_flags) { + ui::MouseEvent press(ui::ET_MOUSE_PRESSED, mouse_position_, mouse_position_, + ui::EventTimeForNow(), mouse_button_flags, + mouse_button_flags); + event_target_->OnMousePressed(press); +} - void PressLeftMouseButton() { PressMouseButton(ui::EF_LEFT_MOUSE_BUTTON); } +void TextfieldTest::ReleaseMouseButton(ui::EventFlags mouse_button_flags) { + ui::MouseEvent release(ui::ET_MOUSE_RELEASED, mouse_position_, + mouse_position_, ui::EventTimeForNow(), + mouse_button_flags, mouse_button_flags); + event_target_->OnMouseReleased(release); +} - void ReleaseLeftMouseButton() { - ReleaseMouseButton(ui::EF_LEFT_MOUSE_BUTTON); - } +void TextfieldTest::PressLeftMouseButton() { + PressMouseButton(ui::EF_LEFT_MOUSE_BUTTON); +} - void ClickLeftMouseButton() { - PressLeftMouseButton(); - ReleaseLeftMouseButton(); - } +void TextfieldTest::ReleaseLeftMouseButton() { + ReleaseMouseButton(ui::EF_LEFT_MOUSE_BUTTON); +} - void ClickRightMouseButton() { - PressMouseButton(ui::EF_RIGHT_MOUSE_BUTTON); - ReleaseMouseButton(ui::EF_RIGHT_MOUSE_BUTTON); - } +void TextfieldTest::ClickLeftMouseButton() { + PressLeftMouseButton(); + ReleaseLeftMouseButton(); +} - void DragMouseTo(const gfx::Point& where) { - mouse_position_ = where; - ui::MouseEvent drag(ui::ET_MOUSE_DRAGGED, where, where, - ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0); - textfield_->OnMouseDragged(drag); - } +void TextfieldTest::ClickRightMouseButton() { + PressMouseButton(ui::EF_RIGHT_MOUSE_BUTTON); + ReleaseMouseButton(ui::EF_RIGHT_MOUSE_BUTTON); +} - // Textfield does not listen to OnMouseMoved, so this function does not send - // an event when it updates the cursor position. - void MoveMouseTo(const gfx::Point& where) { mouse_position_ = where; } +void TextfieldTest::DragMouseTo(const gfx::Point& where) { + mouse_position_ = where; + ui::MouseEvent drag(ui::ET_MOUSE_DRAGGED, where, where, ui::EventTimeForNow(), + ui::EF_LEFT_MOUSE_BUTTON, 0); + event_target_->OnMouseDragged(drag); +} - // Tap on the textfield. - void TapAtCursor(ui::EventPointerType pointer_type) { - ui::GestureEventDetails tap_down_details(ui::ET_GESTURE_TAP_DOWN); - tap_down_details.set_primary_pointer_type(pointer_type); - GestureEventForTest tap_down(GetCursorPositionX(0), 0, tap_down_details); - textfield_->OnGestureEvent(&tap_down); +void TextfieldTest::MoveMouseTo(const gfx::Point& where) { + mouse_position_ = where; +} - ui::GestureEventDetails tap_up_details(ui::ET_GESTURE_TAP); - tap_up_details.set_primary_pointer_type(pointer_type); - GestureEventForTest tap_up(GetCursorPositionX(0), 0, tap_up_details); - textfield_->OnGestureEvent(&tap_up); - } +// Taps on the textfield. +void TextfieldTest::TapAtCursor(ui::EventPointerType pointer_type) { + ui::GestureEventDetails tap_down_details(ui::ET_GESTURE_TAP_DOWN); + tap_down_details.set_primary_pointer_type(pointer_type); + GestureEventForTest tap_down(GetCursorPositionX(0), 0, tap_down_details); + textfield_->OnGestureEvent(&tap_down); - // We need widget to populate wrapper class. - Widget* widget_ = nullptr; - - TestTextfield* textfield_ = nullptr; - std::unique_ptr<TextfieldTestApi> test_api_; - TextfieldModel* model_ = nullptr; - - // The string from Controller::ContentsChanged callback. - base::string16 last_contents_; - - // For testing input method related behaviors. - MockInputMethod* input_method_ = nullptr; - - // Indicates how many times OnBeforeUserAction() is called. - int on_before_user_action_ = 0; - - // Indicates how many times OnAfterUserAction() is called. - int on_after_user_action_ = 0; - - // Position of the mouse for synthetic mouse events. - gfx::Point mouse_position_; - ui::ClipboardBuffer copied_to_clipboard_ = ui::ClipboardBuffer::kMaxValue; - std::unique_ptr<ui::test::EventGenerator> event_generator_; - - private: - DISALLOW_COPY_AND_ASSIGN(TextfieldTest); -}; + ui::GestureEventDetails tap_up_details(ui::ET_GESTURE_TAP); + tap_up_details.set_primary_pointer_type(pointer_type); + GestureEventForTest tap_up(GetCursorPositionX(0), 0, tap_up_details); + textfield_->OnGestureEvent(&tap_up); +} TEST_F(TextfieldTest, ModelChangesTest) { InitTextfield(); @@ -1302,16 +1279,16 @@ TEST_F(TextfieldTest, InsertionDeletionTest) { // Insert a test string in a textfield. InitTextfield(); - for (size_t i = 0; i < 10; i++) + for (size_t i = 0; i < 10; ++i) SendKeyEvent(static_cast<ui::KeyboardCode>(ui::VKEY_A + i)); EXPECT_STR_EQ("abcdefghij", textfield_->GetText()); // Test the delete and backspace keys. textfield_->SetSelectedRange(gfx::Range(5)); - for (size_t i = 0; i < 3; i++) + for (size_t i = 0; i < 3; ++i) SendKeyEvent(ui::VKEY_BACK); EXPECT_STR_EQ("abfghij", textfield_->GetText()); - for (size_t i = 0; i < 3; i++) + for (size_t i = 0; i < 3; ++i) SendKeyEvent(ui::VKEY_DELETE); EXPECT_STR_EQ("abij", textfield_->GetText()); @@ -1743,7 +1720,7 @@ } TEST_F(TextfieldTest, FocusTraversalTest) { - InitTextfields(3); + InitTextfield(3); textfield_->RequestFocus(); EXPECT_EQ(1, GetFocusedView()->GetID()); @@ -2462,7 +2439,7 @@ #if defined(OS_APPLE) TEST_F(TextfieldTest, Yank) { - InitTextfields(2); + InitTextfield(2); textfield_->SetText(ASCIIToUTF16("abcdef")); textfield_->SetSelectedRange(gfx::Range(2, 4)); @@ -3331,7 +3308,7 @@ // Verify that the selection clipboard is not updated for selections on a // password textfield. TEST_F(TextfieldTest, SelectionClipboard_Password) { - InitTextfields(2); + InitTextfield(2); textfield_->SetText(ASCIIToUTF16("abcd")); // Select-all should update the selection clipboard for a non-password @@ -3452,7 +3429,7 @@ TEST_F(TextfieldTest, VirtualKeyboardFocusEnsureCaretNotInRect) { InitTextfield(); - aura::Window* root_window = GetRootWindow(widget_); + aura::Window* root_window = GetRootWindow(widget_.get()); int keyboard_height = 200; gfx::Rect root_bounds = root_window->bounds(); gfx::Rect orig_widget_bounds = gfx::Rect(0, 300, 400, 200); @@ -3747,17 +3724,13 @@ // Verify that after creating a new Textfield, the Textfield doesn't // automatically receive focus and the text cursor is not visible. TEST_F(TextfieldTest, TextfieldInitialization) { - TestTextfield* new_textfield = new TestTextfield(); - new_textfield->set_controller(this); - Widget* widget(new Widget()); - Widget::InitParams params = - CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); - params.bounds = gfx::Rect(100, 100, 100, 100); - widget->Init(std::move(params)); + std::unique_ptr<Widget> widget = CreateTestWidget(); View* container = widget->SetContentsView(std::make_unique<View>()); - container->AddChildView(new_textfield); - new_textfield->SetBoundsRect(params.bounds); + TestTextfield* new_textfield = + container->AddChildView(std::make_unique<TestTextfield>()); + new_textfield->set_controller(this); + new_textfield->SetBoundsRect(gfx::Rect(100, 100, 100, 100)); new_textfield->SetID(1); test_api_ = std::make_unique<TextfieldTestApi>(new_textfield); widget->Show(); @@ -4015,6 +3988,7 @@ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, textfield_->GetFocusReason()); + // Pen tap, followed by a touch tap. TapAtCursor(ui::EventPointerType::kPen); TapAtCursor(ui::EventPointerType::kTouch); EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_PEN, @@ -4173,4 +4147,5 @@ ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT)); #endif } +} // namespace test } // namespace views
diff --git a/ui/views/controls/textfield/textfield_unittest.h b/ui/views/controls/textfield/textfield_unittest.h new file mode 100644 index 0000000..daea347 --- /dev/null +++ b/ui/views/controls/textfield/textfield_unittest.h
@@ -0,0 +1,184 @@ +// Copyright 2019 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 UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_UNITTEST_H_ +#define UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_UNITTEST_H_ + +#include "ui/views/controls/textfield/textfield.h" + +#include <memory> +#include <string> +#include <utility> + +#include "ui/base/clipboard/clipboard.h" +#include "ui/events/event_constants.h" +#include "ui/views/controls/textfield/textfield_controller.h" +#include "ui/views/test/views_test_base.h" + +#define EXPECT_STR_EQ(ascii, utf16) EXPECT_EQ(base::ASCIIToUTF16(ascii), utf16) + +namespace ui { +namespace test { +class EventGenerator; +} +} // namespace ui + +namespace views { + +class TextfieldTestApi; + +namespace test { + +class MockInputMethod; +class TestTextfield; + +class TextfieldTest : public ViewsTestBase, public TextfieldController { + public: + TextfieldTest(); + ~TextfieldTest() override; + + // ViewsTestBase: + void SetUp() override; + void TearDown() override; + + ui::ClipboardBuffer GetAndResetCopiedToClipboard(); + base::string16 GetClipboardText(ui::ClipboardBuffer type); + void SetClipboardText(ui::ClipboardBuffer type, const std::string& text); + + // TextfieldController: + void ContentsChanged(Textfield* sender, + const base::string16& new_contents) override; + void OnBeforeUserAction(Textfield* sender) override; + void OnAfterUserAction(Textfield* sender) override; + void OnAfterCutOrCopy(ui::ClipboardBuffer clipboard_type) override; + + void InitTextfield(int count = 1); + ui::MenuModel* GetContextMenuModel(); + + bool TestingNativeMac() const; + bool TestingNativeCrOs() const; + + template <typename T> + T* PrepareTextfields(int count, + std::unique_ptr<T> textfield_owned, + gfx::Rect bounds) { + widget_ = CreateTestWidget(); + widget_->SetBounds(bounds); + + View* container = widget_->SetContentsView(std::make_unique<View>()); + T* textfield = container->AddChildView(std::move(textfield_owned)); + + PrepareTextfieldsInternal(count, textfield, container, bounds); + + return textfield; + } + + protected: + void PrepareTextfieldsInternal(int count, + Textfield* textfield, + View* view, + gfx::Rect bounds); + + void SendKeyPress(ui::KeyboardCode key_code, int flags); + void SendKeyEvent(ui::KeyboardCode key_code, + bool alt, + bool shift, + bool control_or_command, + bool caps_lock); + void SendKeyEvent(ui::KeyboardCode key_code, + bool shift, + bool control_or_command); + void SendKeyEvent(ui::KeyboardCode key_code); + void SendKeyEvent(base::char16 ch); + void SendKeyEvent(base::char16 ch, int flags); + void SendKeyEvent(base::char16 ch, int flags, bool from_vk); + void DispatchMockInputMethodKeyEvent(); + + // Sends a platform-specific move (and select) to the logical start of line. + // Eg. this should move (and select) to the right end of line for RTL text. + virtual void SendHomeEvent(bool shift); + + // Sends a platform-specific move (and select) to the logical end of line. + virtual void SendEndEvent(bool shift); + + // Sends {delete, move, select} word {forward, backward}. + void SendWordEvent(ui::KeyboardCode key, bool shift); + + // Sends Shift+Delete if supported, otherwise Cmd+X again. + void SendAlternateCut(); + + // Sends Ctrl+Insert if supported, otherwise Cmd+C again. + void SendAlternateCopy(); + + // Sends Shift+Insert if supported, otherwise Cmd+V again. + void SendAlternatePaste(); + + View* GetFocusedView(); + int GetCursorPositionX(int cursor_pos); + int GetCursorYForTesting(); + + // Get the current cursor bounds. + gfx::Rect GetCursorBounds(); + + // Get the cursor bounds of |sel|. + gfx::Rect GetCursorBounds(const gfx::SelectionModel& sel); + + gfx::Rect GetDisplayRect(); + gfx::Rect GetCursorViewRect(); + + // Mouse click on the point whose x-axis is |bound|'s x plus |x_offset| and + // y-axis is in the middle of |bound|'s vertical range. + void MouseClick(const gfx::Rect bound, int x_offset); + + // This is to avoid double/triple click. + void NonClientMouseClick(); + + void VerifyTextfieldContextMenuContents(bool textfield_has_selection, + bool can_undo, + ui::MenuModel* menu); + void PressMouseButton(ui::EventFlags mouse_button_flags); + void ReleaseMouseButton(ui::EventFlags mouse_button_flags); + void PressLeftMouseButton(); + void ReleaseLeftMouseButton(); + void ClickLeftMouseButton(); + void ClickRightMouseButton(); + void DragMouseTo(const gfx::Point& where); + + // Textfield does not listen to OnMouseMoved, so this function does not send + // an event when it updates the cursor position. + void MoveMouseTo(const gfx::Point& where); + void TapAtCursor(ui::EventPointerType pointer_type); + + // We need widget to populate wrapper class. + std::unique_ptr<Widget> widget_ = nullptr; + + TestTextfield* textfield_ = nullptr; + std::unique_ptr<TextfieldTestApi> test_api_; + TextfieldModel* model_ = nullptr; + + // The string from Controller::ContentsChanged callback. + base::string16 last_contents_; + + // For testing input method related behaviors. + MockInputMethod* input_method_ = nullptr; + + // Indicates how many times OnBeforeUserAction() is called. + int on_before_user_action_ = 0; + + // Indicates how many times OnAfterUserAction() is called. + int on_after_user_action_ = 0; + + // Position of the mouse for synthetic mouse events. + gfx::Point mouse_position_; + + ui::ClipboardBuffer copied_to_clipboard_ = ui::ClipboardBuffer::kMaxValue; + std::unique_ptr<ui::test::EventGenerator> event_generator_; + View* event_target_ = nullptr; +}; + +} // namespace test + +} // namespace views + +#endif // UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_UNITTEST_H_
diff --git a/ui/views/examples/BUILD.gn b/ui/views/examples/BUILD.gn index 0908b991..8b1471b 100644 --- a/ui/views/examples/BUILD.gn +++ b/ui/views/examples/BUILD.gn
@@ -69,6 +69,8 @@ "table_example.h", "text_example.cc", "text_example.h", + "textarea_example.cc", + "textarea_example.h", "textfield_example.cc", "textfield_example.h", "throbber_example.cc",
diff --git a/ui/views/examples/create_examples.cc b/ui/views/examples/create_examples.cc index 1046b4a..112d0b5 100644 --- a/ui/views/examples/create_examples.cc +++ b/ui/views/examples/create_examples.cc
@@ -30,6 +30,7 @@ #include "ui/views/examples/tabbed_pane_example.h" #include "ui/views/examples/table_example.h" #include "ui/views/examples/text_example.h" +#include "ui/views/examples/textarea_example.h" #include "ui/views/examples/textfield_example.h" #include "ui/views/examples/throbber_example.h" #include "ui/views/examples/toggle_button_example.h" @@ -67,6 +68,7 @@ examples.push_back(std::make_unique<TabbedPaneExample>()); examples.push_back(std::make_unique<TableExample>()); examples.push_back(std::make_unique<TextExample>()); + examples.push_back(std::make_unique<TextareaExample>()); examples.push_back(std::make_unique<TextfieldExample>()); examples.push_back(std::make_unique<ToggleButtonExample>()); examples.push_back(std::make_unique<ThrobberExample>());
diff --git a/ui/views/examples/textarea_example.cc b/ui/views/examples/textarea_example.cc new file mode 100644 index 0000000..cf1ddf2 --- /dev/null +++ b/ui/views/examples/textarea_example.cc
@@ -0,0 +1,36 @@ +// Copyright 2019 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 "ui/views/examples/textarea_example.h" + +#include <memory> +#include <utility> + +#include "base/strings/utf_string_conversions.h" +#include "ui/views/controls/textarea/textarea.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/view.h" + +namespace views { +namespace examples { + +TextareaExample::TextareaExample() : ExampleBase("Textarea") {} + +void TextareaExample::CreateExampleView(View* container) { + constexpr char kLongText[] = + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod" + " tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate " + "velit esse cillum dolore eu fugiat nulla pariatur.\n\nExcepteur sint " + "occaecat cupidatat non proident, sunt in culpa qui officia deserunt " + "mollit anim id est laborum."; + auto textarea = std::make_unique<Textarea>(); + textarea->SetText(base::UTF8ToUTF16(kLongText)); + container->SetLayoutManager(std::make_unique<views::FillLayout>()); + container->AddChildView(std::move(textarea)); +} + +} // namespace examples +} // namespace views
diff --git a/ui/views/examples/textarea_example.h b/ui/views/examples/textarea_example.h new file mode 100644 index 0000000..2fdf1a8 --- /dev/null +++ b/ui/views/examples/textarea_example.h
@@ -0,0 +1,26 @@ +// Copyright 2019 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 UI_VIEWS_EXAMPLES_TEXTAREA_EXAMPLE_H_ +#define UI_VIEWS_EXAMPLES_TEXTAREA_EXAMPLE_H_ + +#include "base/macros.h" +#include "ui/views/examples/example_base.h" + +namespace views { +namespace examples { + +class VIEWS_EXAMPLES_EXPORT TextareaExample : public ExampleBase { + public: + TextareaExample(); + ~TextareaExample() override = default; + + // ExampleBase: + void CreateExampleView(View* container) override; +}; + +} // namespace examples +} // namespace views + +#endif // UI_VIEWS_EXAMPLES_TEXTAREA_EXAMPLE_H_
diff --git a/ui/webui/resources/cr_components/customize_themes/customize_themes.html b/ui/webui/resources/cr_components/customize_themes/customize_themes.html index e6eeeb1d..e4a252f 100644 --- a/ui/webui/resources/cr_components/customize_themes/customize_themes.html +++ b/ui/webui/resources/cr_components/customize_themes/customize_themes.html
@@ -148,7 +148,8 @@ </div> <cr-grid id="themesContainer" columns="6" role="radiogroup"> <div aria-label="[[i18n('colorPickerLabel')]]" - tabindex="0" on-click="onAutogeneratedThemeClick_" role="radio" + tabindex$="[[getTabIndex_('autogenerated', selectedTheme)]]" + on-click="onAutogeneratedThemeClick_" role="radio" aria-checked$="[[getThemeIconCheckedStatus_('autogenerated', selectedTheme)]]"> <div id="autogeneratedThemeContainer"> <cr-theme-icon id="autogeneratedTheme" @@ -163,7 +164,8 @@ </paper-tooltip> </div> <div aria-label="[[i18n('defaultThemeLabel')]]" - tabindex="0" on-click="onDefaultThemeClick_" role="radio" + tabindex$="[[getTabIndex_('default', selectedTheme)]]" + on-click="onDefaultThemeClick_" role="radio" aria-checked$="[[getThemeIconCheckedStatus_('default', selectedTheme)]]"> <cr-theme-icon id="defaultTheme" selected$="[[isThemeIconSelected_('default', selectedTheme)]]"> @@ -173,7 +175,8 @@ </paper-tooltip> </div> <template is="dom-repeat" id="themes" items="[[chromeThemes_]]"> - <div aria-label="[[item.label]]" tabindex="0" + <div aria-label="[[item.label]]" + tabindex$="[[getTabIndex_(item.id, selectedTheme)]]" on-click="onChromeThemeClick_" class="chrome-theme-wrapper" role="radio" aria-checked$="[[getThemeIconCheckedStatus_(item.id, selectedTheme)]]"> <cr-theme-icon
diff --git a/ui/webui/resources/cr_components/customize_themes/customize_themes.js b/ui/webui/resources/cr_components/customize_themes/customize_themes.js index 68acbe6..f1260fcf 100644 --- a/ui/webui/resources/cr_components/customize_themes/customize_themes.js +++ b/ui/webui/resources/cr_components/customize_themes/customize_themes.js
@@ -40,10 +40,11 @@ return { /** * An object describing the currently selected theme. - * @type {!Theme} + * @type {?Theme} */ selectedTheme: { type: Object, + value: null, observer: 'onThemeChange_', notify: true, }, @@ -143,7 +144,8 @@ /** @private */ onThemeChange_() { - if (this.selectedTheme.type !== ThemeType.kAutogenerated) { + if (!this.selectedTheme || + this.selectedTheme.type !== ThemeType.kAutogenerated) { return; } const rgbaFrameColor = @@ -202,6 +204,18 @@ * @return {string} * @private */ + getTabIndex_(id) { + if (!this.selectedTheme && id === 'autogenerated') { + return '0'; + } + return this.isThemeIconSelected_(id) ? '0' : '-1'; + } + + /** + * @param {string|number} id + * @return {string} + * @private + */ getThemeIconCheckedStatus_(id) { return this.isThemeIconSelected_(id) ? 'true' : 'false'; } @@ -211,7 +225,8 @@ * @private */ isThirdPartyTheme_() { - return this.selectedTheme.type === ThemeType.kThirdParty; + return !!this.selectedTheme && + this.selectedTheme.type === ThemeType.kThirdParty; } /**
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/payments/WebLayerPaymentRequestFactory.java b/weblayer/browser/java/org/chromium/weblayer_private/payments/WebLayerPaymentRequestFactory.java index c78d931..6b1a565 100644 --- a/weblayer/browser/java/org/chromium/weblayer_private/payments/WebLayerPaymentRequestFactory.java +++ b/weblayer/browser/java/org/chromium/weblayer_private/payments/WebLayerPaymentRequestFactory.java
@@ -21,8 +21,6 @@ import org.chromium.content_public.browser.RenderFrameHost; import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContentsStatics; -import org.chromium.mojo.system.MojoException; -import org.chromium.mojo.system.MojoResult; import org.chromium.payments.mojom.PaymentRequest; import org.chromium.services.service_manager.InterfaceFactory; import org.chromium.url.GURL; @@ -107,8 +105,7 @@ public PaymentRequest createImpl() { if (mRenderFrameHost == null) return new InvalidPaymentRequest(); if (!mRenderFrameHost.isFeatureEnabled(FeaturePolicyFeature.PAYMENT)) { - mRenderFrameHost.getRemoteInterfaces().onConnectionError( - new MojoException(MojoResult.PERMISSION_DENIED)); + mRenderFrameHost.terminateRendererDueToBadMessage(241 /*PAYMENTS_WITHOUT_PERMISSION*/); return null; }
diff --git a/weblayer/browser/webrtc/media_stream_manager.cc b/weblayer/browser/webrtc/media_stream_manager.cc index a025d45a..967fdc4 100644 --- a/weblayer/browser/webrtc/media_stream_manager.cc +++ b/weblayer/browser/webrtc/media_stream_manager.cc
@@ -69,9 +69,6 @@ } void OnDeviceStopped(const std::string& label, const content::DesktopMediaID& media_id) override {} - void SetStopCallback(base::OnceClosure stop) override { - stop_ = std::move(stop); - } bool streaming_audio() const { return streaming_audio_; }