diff --git a/DEPS b/DEPS index 330c194..395f05ee 100644 --- a/DEPS +++ b/DEPS
@@ -310,15 +310,15 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'src_internal_revision': 'e579b0fc1f54bd6660d0b7233d46be65f0ad5c70', + 'src_internal_revision': '5602d5f24b9b4fbdad2cdee2b6a6ea8a701a4e33', # 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': 'fb2e9d308c35b4964cf7a3dc545d6587de708e64', + 'skia_revision': '233c4f26427ae076c1287d380cbb6a50a6618c71', # 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': 'e90232433eb667a9127dd002087bf1e6fc536612', + 'v8_revision': 'dc39a645a1a7f71ec14f82f77c95c56054a98f00', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. @@ -337,7 +337,7 @@ # # Note this revision should be updated with # third_party/boringssl/roll_boringssl.py, not roll-dep. - 'boringssl_revision': '180066d66d469c26ca605f522bf5c1f08547be3e', + 'boringssl_revision': 'b8e012e1ff736cc794273af4a7db521e6b18bcd5', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Fuchsia sdk # and whatever else without interference from each other. @@ -385,11 +385,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. - 'catapult_revision': '84747457395344276fbbe5b6e227f6daba6a06cb', + 'catapult_revision': '49dd56f508c7c8acd8802155293f6678c4f8606a', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling chromium_variations # and whatever else without interference from each other. - 'chromium_variations_revision': 'a558a696c746c3c7a7d2596f5ab9c284a58c3543', + 'chromium_variations_revision': '0da8cffe0a5cf9b1a4b13bc9520318016d6017dc', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling CrossBench # and whatever else without interference from each other. @@ -445,7 +445,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'dawn_revision': '0ca4bc0e5dfd24b03e039c4bd9bfe540f82e3846', + 'dawn_revision': '7803cd75ecd62a1ec7d6dc479d558ae84640fc44', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -837,7 +837,7 @@ 'src/clank': { 'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' + - '5d3f1ca9ce9258880f0c1fb106fdfe71faeb8178', + 'f1a3d5adac86bd8c96767f9f69112b8a6e9bccca', 'condition': 'checkout_android and checkout_src_internal', }, @@ -846,7 +846,7 @@ }, 'src/ios/third_party/earl_grey2/src': { - 'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + '3f82729077f1df6928abd7a0385e786c1c66d0cf', + 'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + '59c8a057e6e09bf39b265dddb58b27d09bbf3d6c', 'condition': 'checkout_ios', }, @@ -1032,7 +1032,7 @@ 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': '72OrhoxRCpPaLpIr0nDrlhaTfN8VorgC81HCZrVo5sMC', + 'version': 'GwPHeH5nDbKXURzYxNpCMayK7NE1pOW-GpMPLDYGKK8C', }, ], 'condition': 'checkout_android', @@ -1282,7 +1282,7 @@ Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'), 'src/third_party/devtools-frontend-internal': { - 'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '0b8db14fea288edd82c1da00f68c3665bd703eff', + 'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'bee9175da130251d9229129f9ad53abab204b981', 'condition': 'checkout_src_internal', }, @@ -1740,7 +1740,7 @@ Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '09a4f3ec842a8932341b195c5b01e141c8a16eb7', 'src/third_party/openscreen/src': - Var('chromium_git') + '/openscreen' + '@' + '827bafeedf78eec2ea706dfe6e39f485aa2e211f', + Var('chromium_git') + '/openscreen' + '@' + 'b4cdfcf00681d1952ad48284b7bdb6c3bcba1360', 'src/third_party/openxr/src': { 'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '58a00cf85c39ad5ec4dc43a769624e420c06179a', @@ -1751,7 +1751,7 @@ Var('pdfium_git') + '/pdfium.git' + '@' + Var('pdfium_revision'), 'src/third_party/perfetto': - Var('android_git') + '/platform/external/perfetto.git' + '@' + '42a117e9cd3ff638304b917de756d99644e13685', + Var('android_git') + '/platform/external/perfetto.git' + '@' + '029945afde0d4e9781174b207f248922c66d8213', 'src/third_party/perl': { 'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3', @@ -1896,7 +1896,7 @@ 'dep_type': 'cipd', }, - 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@53e39be12b9e2443f6829d6bae3ec8ab7ccd23ef', + 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@9a8701af9cef9c585afa648187bfb2ac3f7482af', 'src/third_party/vulkan_memory_allocator': Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'e87036508bb156f9986ea959323de1869e328f58', @@ -1933,10 +1933,10 @@ Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '0343b5704f8abde7662c5eb87a06b52ce0b7f8ca', 'src/third_party/webgpu-cts/src': - Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '78c19b71620af3c7302041397a186055b08a405d', + Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'd2d4ab2cfaabd01982c2445277350efa3b7f437b', 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + '1e42d83db4f1fb246dc992e22f6cbe6182072157', + Var('webrtc_git') + '/src.git' + '@' + 'ee15bea056e2185a9c55945bd99b9b19e41a4495', # Wuffs' canonical repository is at github.com/google/wuffs, but we use # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file. @@ -4234,7 +4234,7 @@ 'src/ios_internal': { 'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' + - '5f249148084fec1b372a4cff918c1317e7401dee', + '3bd4649ccd326d71f1e65481844370406fc32aea', 'condition': 'checkout_ios and checkout_src_internal', },
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn index 2dd5b7b33..ba2189ba 100644 --- a/android_webview/BUILD.gn +++ b/android_webview/BUILD.gn
@@ -1103,27 +1103,29 @@ } } -grit("generate_components_scaled_resources") { - source = "../components/resources/components_scaled_resources.grd" +if (webview_includes_weblayer) { + grit("generate_components_scaled_resources") { + source = "../components/resources/components_scaled_resources.grd" - # See :generate_webui_resources for an explanation of the allowlist - _allowlist = rebase_path("$target_gen_dir/grit_resources_allowlist.txt", - root_build_dir) + # See :generate_webui_resources for an explanation of the allowlist + _allowlist = rebase_path("$target_gen_dir/grit_resources_allowlist.txt", + root_build_dir) - grit_flags = [ - "-w", - _allowlist, - ] - outputs = [ - "grit/components_scaled_resources.h", - "grit/components_scaled_resources_map.cc", - "grit/components_scaled_resources_map.h", - "components_resources_100_percent.pak", - "components_resources_200_percent.pak", - "components_resources_300_percent.pak", - ] + grit_flags = [ + "-w", + _allowlist, + ] + outputs = [ + "grit/components_scaled_resources.h", + "grit/components_scaled_resources_map.cc", + "grit/components_scaled_resources_map.h", + "components_resources_100_percent.pak", + "components_resources_200_percent.pak", + "components_resources_300_percent.pak", + ] - deps = [ ":concatenate_resources_allowlists" ] + deps = [ ":concatenate_resources_allowlists" ] + } } grit("generate_components_strings") {
diff --git a/android_webview/expectations/trichrome_webview_bundle.arm.libs_and_assets.expected b/android_webview/expectations/trichrome_webview_bundle.arm.libs_and_assets.expected index 0b72da8c..9b76ad74 100644 --- a/android_webview/expectations/trichrome_webview_bundle.arm.libs_and_assets.expected +++ b/android_webview/expectations/trichrome_webview_bundle.arm.libs_and_assets.expected
@@ -1,85 +1,4 @@ apk_path=assets/chrome_100_percent.pak, compress=False, alignment=4 -apk_path=assets/locales/af.pak, compress=False, alignment=4 -apk_path=assets/locales/am.pak, compress=False, alignment=4 -apk_path=assets/locales/ar.pak, compress=False, alignment=4 -apk_path=assets/locales/as.pak, compress=False, alignment=4 -apk_path=assets/locales/az.pak, compress=False, alignment=4 -apk_path=assets/locales/be.pak, compress=False, alignment=4 -apk_path=assets/locales/bg.pak, compress=False, alignment=4 -apk_path=assets/locales/bn.pak, compress=False, alignment=4 -apk_path=assets/locales/bs.pak, compress=False, alignment=4 -apk_path=assets/locales/ca.pak, compress=False, alignment=4 -apk_path=assets/locales/cs.pak, compress=False, alignment=4 -apk_path=assets/locales/da.pak, compress=False, alignment=4 -apk_path=assets/locales/de.pak, compress=False, alignment=4 -apk_path=assets/locales/el.pak, compress=False, alignment=4 -apk_path=assets/locales/en-GB.pak, compress=False, alignment=4 -apk_path=assets/locales/en-US.pak, compress=False, alignment=4 -apk_path=assets/locales/es-419.pak, compress=False, alignment=4 -apk_path=assets/locales/es.pak, compress=False, alignment=4 -apk_path=assets/locales/et.pak, compress=False, alignment=4 -apk_path=assets/locales/eu.pak, compress=False, alignment=4 -apk_path=assets/locales/fa.pak, compress=False, alignment=4 -apk_path=assets/locales/fi.pak, compress=False, alignment=4 -apk_path=assets/locales/fil.pak, compress=False, alignment=4 -apk_path=assets/locales/fr-CA.pak, compress=False, alignment=4 -apk_path=assets/locales/fr.pak, compress=False, alignment=4 -apk_path=assets/locales/gl.pak, compress=False, alignment=4 -apk_path=assets/locales/gu.pak, compress=False, alignment=4 -apk_path=assets/locales/he.pak, compress=False, alignment=4 -apk_path=assets/locales/hi.pak, compress=False, alignment=4 -apk_path=assets/locales/hr.pak, compress=False, alignment=4 -apk_path=assets/locales/hu.pak, compress=False, alignment=4 -apk_path=assets/locales/hy.pak, compress=False, alignment=4 -apk_path=assets/locales/id.pak, compress=False, alignment=4 -apk_path=assets/locales/is.pak, compress=False, alignment=4 -apk_path=assets/locales/it.pak, compress=False, alignment=4 -apk_path=assets/locales/ja.pak, compress=False, alignment=4 -apk_path=assets/locales/ka.pak, compress=False, alignment=4 -apk_path=assets/locales/kk.pak, compress=False, alignment=4 -apk_path=assets/locales/km.pak, compress=False, alignment=4 -apk_path=assets/locales/kn.pak, compress=False, alignment=4 -apk_path=assets/locales/ko.pak, compress=False, alignment=4 -apk_path=assets/locales/ky.pak, compress=False, alignment=4 -apk_path=assets/locales/lo.pak, compress=False, alignment=4 -apk_path=assets/locales/lt.pak, compress=False, alignment=4 -apk_path=assets/locales/lv.pak, compress=False, alignment=4 -apk_path=assets/locales/mk.pak, compress=False, alignment=4 -apk_path=assets/locales/ml.pak, compress=False, alignment=4 -apk_path=assets/locales/mn.pak, compress=False, alignment=4 -apk_path=assets/locales/mr.pak, compress=False, alignment=4 -apk_path=assets/locales/ms.pak, compress=False, alignment=4 -apk_path=assets/locales/my.pak, compress=False, alignment=4 -apk_path=assets/locales/nb.pak, compress=False, alignment=4 -apk_path=assets/locales/ne.pak, compress=False, alignment=4 -apk_path=assets/locales/nl.pak, compress=False, alignment=4 -apk_path=assets/locales/or.pak, compress=False, alignment=4 -apk_path=assets/locales/pa.pak, compress=False, alignment=4 -apk_path=assets/locales/pl.pak, compress=False, alignment=4 -apk_path=assets/locales/pt-BR.pak, compress=False, alignment=4 -apk_path=assets/locales/pt-PT.pak, compress=False, alignment=4 -apk_path=assets/locales/ro.pak, compress=False, alignment=4 -apk_path=assets/locales/ru.pak, compress=False, alignment=4 -apk_path=assets/locales/si.pak, compress=False, alignment=4 -apk_path=assets/locales/sk.pak, compress=False, alignment=4 -apk_path=assets/locales/sl.pak, compress=False, alignment=4 -apk_path=assets/locales/sq.pak, compress=False, alignment=4 -apk_path=assets/locales/sr-Latn.pak, compress=False, alignment=4 -apk_path=assets/locales/sr.pak, compress=False, alignment=4 -apk_path=assets/locales/sv.pak, compress=False, alignment=4 -apk_path=assets/locales/sw.pak, compress=False, alignment=4 -apk_path=assets/locales/ta.pak, compress=False, alignment=4 -apk_path=assets/locales/te.pak, compress=False, alignment=4 -apk_path=assets/locales/th.pak, compress=False, alignment=4 -apk_path=assets/locales/tr.pak, compress=False, alignment=4 -apk_path=assets/locales/uk.pak, compress=False, alignment=4 -apk_path=assets/locales/ur.pak, compress=False, alignment=4 -apk_path=assets/locales/uz.pak, compress=False, alignment=4 -apk_path=assets/locales/vi.pak, compress=False, alignment=4 -apk_path=assets/locales/zh-CN.pak, compress=False, alignment=4 -apk_path=assets/locales/zh-HK.pak, compress=False, alignment=4 -apk_path=assets/locales/zh-TW.pak, compress=False, alignment=4 -apk_path=assets/locales/zu.pak, compress=False, alignment=4 apk_path=assets/resources.pak, compress=False, alignment=4 apk_path=assets/stored-locales/af.pak, compress=False, alignment=4 apk_path=assets/stored-locales/am.pak, compress=False, alignment=4
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java index 4750ce8..6d61b44 100644 --- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java +++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -187,9 +187,6 @@ Flag.baseFeature(AutofillFeatures.AUTOFILL_ENABLE_SUPPORT_FOR_PHONE_NUMBER_TRUNK_TYPES, "Rationalizes city-and-number and city-code fields to the " + "correct trunk-prefix types."), - Flag.baseFeature(AutofillFeatures.AUTOFILL_ENFORCE_DELAYS_IN_STRIKE_DATABASE, - "Enforce delay between offering Autofill opportunities in the " - + "strike database."), Flag.baseFeature(AutofillFeatures.AUTOFILL_PARSE_ASYNC, "Parse forms asynchronously outside of the UI thread."), Flag.baseFeature(AutofillFeatures.AUTOFILL_PARSING_PATTERN_PROVIDER,
diff --git a/android_webview/system_webview_bundle.gni b/android_webview/system_webview_bundle.gni index 97fa93f..73fa22ff 100644 --- a/android_webview/system_webview_bundle.gni +++ b/android_webview/system_webview_bundle.gni
@@ -10,19 +10,20 @@ template("system_webview_bundle") { _is_trichrome = defined(invoker.is_trichrome) && invoker.is_trichrome - _base_target_name = get_label_info(invoker.base_module_target, "name") - _base_target_gen_dir = - get_label_info(invoker.base_module_target, "target_gen_dir") - _base_module_build_config = - "$_base_target_gen_dir/${_base_target_name}.build_config.json" - _rebased_base_module_build_config = - rebase_path(_base_module_build_config, root_build_dir) - _base_module_version_code = - "@FileArg($_rebased_base_module_build_config:deps_info:version_code)" assert(_is_trichrome == defined(invoker.static_library_provider)) if (webview_includes_weblayer) { + _base_target_name = get_label_info(invoker.base_module_target, "name") + _base_target_gen_dir = + get_label_info(invoker.base_module_target, "target_gen_dir") + _base_module_build_config = + "$_base_target_gen_dir/${_base_target_name}.build_config.json" + _rebased_base_module_build_config = + rebase_path(_base_module_build_config, root_build_dir) + _base_module_version_code = + "@FileArg($_rebased_base_module_build_config:deps_info:version_code)" + # TODO(crbug.com/1105096): If WebView starts using # //components/module_installer, it will probably make sense to refactor # chrome_feature_module() to be used here.
diff --git a/android_webview/tools/captured_sites_tests/javatests/src/org/chromium/webview_ui_test/test/util/PerformActions.java b/android_webview/tools/captured_sites_tests/javatests/src/org/chromium/webview_ui_test/test/util/PerformActions.java index 33e3a547..6579262b 100644 --- a/android_webview/tools/captured_sites_tests/javatests/src/org/chromium/webview_ui_test/test/util/PerformActions.java +++ b/android_webview/tools/captured_sites_tests/javatests/src/org/chromium/webview_ui_test/test/util/PerformActions.java
@@ -107,10 +107,10 @@ UiObject2 firstAutofill = mDevice.findObject(By.res("org.chromium.webview_ui_test", "text")); if (firstAutofill == null) { - Log.d(TAG, "Autofill element is not found"); + Log.d(TAG, "Autofill element was not found"); return false; } else { - Log.d(TAG, "Autofill element is not found"); + Log.d(TAG, "Autofill element was found"); firstAutofill.click(); return true; }
diff --git a/ash/accelerators/ash_accelerator_configuration.cc b/ash/accelerators/ash_accelerator_configuration.cc index bb8ae24..8b441c5 100644 --- a/ash/accelerators/ash_accelerator_configuration.cc +++ b/ash/accelerators/ash_accelerator_configuration.cc
@@ -650,17 +650,25 @@ } void AshAcceleratorConfiguration::ApplyPrefOverrides() { + // Stores all actions with prefs to be removed, this gets populated if there + // are malformed prefs which results in an empty pref after removal. + std::vector<uint32_t> actions_to_be_removed; + for (auto entry : accelerator_overrides_) { int action_id; base::StringToInt(entry.first, &action_id); - CHECK(IsValid(action_id)); + if (!IsValid(action_id)) { + actions_to_be_removed.push_back(action_id); + continue; + } - const base::Value::List& override_list = entry.second.GetList(); + base::Value::List& override_list = entry.second.GetList(); CHECK(!override_list.empty()); - for (const auto& accelerator_override : override_list) { - const base::Value::Dict& override_dict = accelerator_override.GetDict(); - const AcceleratorModificationData& override_data = + auto override_list_iter = override_list.begin(); + while (override_list_iter != override_list.end()) { + base::Value::Dict& override_dict = override_list_iter->GetDict(); + AcceleratorModificationData override_data = ValueToAcceleratorModificationData(override_dict); if (override_data.action == AcceleratorModificationAction::kRemove) { // Race condition: @@ -668,7 +676,19 @@ // it to another action, we do not attempt to remove here. const auto* found_id = accelerator_to_id_.Find(override_data.accelerator); - CHECK(found_id); + + // If the pref has an accelerator that is invalid, do not attempt to + // apply the pref and remove it. + if (!found_id) { + override_list_iter = override_list.erase(override_list_iter); + // If removing the pref results in an empty pref, remove it and move + // onto the next override pref. + if (override_list.empty()) { + actions_to_be_removed.push_back(action_id); + break; + } + continue; + } if (*found_id == action_id) { DoRemoveAccelerator(action_id, override_data.accelerator, /*save_override=*/false); @@ -679,12 +699,17 @@ DoAddAccelerator(action_id, override_data.accelerator, /*save_override=*/false); } + ++override_list_iter; } } - // Check if the overriden accelerators are valid, if not then restore all + // Remove all empty override prefs. + for (uint32_t action : actions_to_be_removed) { + accelerator_overrides_.Remove(base::NumberToString(action)); + } + + // Check if the overridden accelerators are valid, if not then restore all // defaults. - // TODO(jimmyxgong): Determine if we should also reset the pref. if (!AreAcceleratorsValid()) { RestoreAllDefaults(); }
diff --git a/ash/accelerators/ash_accelerator_configuration_unittest.cc b/ash/accelerators/ash_accelerator_configuration_unittest.cc index 904a1ff..5d7029513 100644 --- a/ash/accelerators/ash_accelerator_configuration_unittest.cc +++ b/ash/accelerators/ash_accelerator_configuration_unittest.cc
@@ -35,6 +35,8 @@ constexpr char kAcceleratorModifiersKey[] = "modifiers"; constexpr char kAcceleratorKeyCodeKey[] = "key"; constexpr char kAcceleratorModificationActionKey[] = "action"; +constexpr char kAcceleratorStateKey[] = "state"; +constexpr char kAcceleratorTypeKey[] = "type"; constexpr char kFakeUserEmail[] = "fakeuser@gmail.com"; constexpr char kFakeUserEmail2[] = "fakeuser2@gmail.com"; @@ -60,6 +62,24 @@ int num_times_accelerator_updated_called_ = 0; }; +base::Value AcceleratorModificationDataToValue( + const ui::Accelerator& accelerator, + AcceleratorModificationAction action) { + base::Value::Dict accelerator_values; + accelerator_values.Set(kAcceleratorModifiersKey, accelerator.modifiers()); + accelerator_values.Set(kAcceleratorKeyCodeKey, + static_cast<int>(accelerator.key_code())); + accelerator_values.Set( + kAcceleratorTypeKey, + static_cast<int>(ash::mojom::AcceleratorType::kDefault)); + accelerator_values.Set( + kAcceleratorStateKey, + static_cast<int>(ash::mojom::AcceleratorState::kEnabled)); + accelerator_values.Set(kAcceleratorModificationActionKey, + static_cast<int>(action)); + return base::Value(std::move(accelerator_values)); +} + base::Value::Dict GetOverridePref() { return ash::Shell::Get() ->session_controller() @@ -68,6 +88,18 @@ .Clone(); } +void SetOverridePref(const ui::Accelerator& accelerator, + AcceleratorModificationAction action, + uint32_t action_id) { + base::Value::List override_list; + override_list.Append(AcceleratorModificationDataToValue(accelerator, action)); + + base::Value::Dict overrides; + overrides.Set(base::NumberToString(action_id), std::move(override_list)); + ash::Shell::Get()->session_controller()->GetActivePrefService()->SetDict( + ash::prefs::kShortcutCustomizationOverrides, std::move(overrides)); +} + AcceleratorModificationData ValueToAcceleratorModificationData( const base::Value::Dict& value) { absl::optional<int> keycode = value.FindInt(kAcceleratorKeyCodeKey); @@ -547,10 +579,10 @@ AcceleratorModificationData override_data = ValueToAcceleratorModificationData( accelerator_overrides->front().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - override_data.accelerator); + override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kRemove, override_data.action); // Compare expected accelerators and that the observer was fired after @@ -1450,10 +1482,10 @@ AcceleratorModificationData override_data = ValueToAcceleratorModificationData( accelerator_overrides->front().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - override_data.accelerator); + override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kRemove, override_data.action); // Simulate login on another account, expect the pref to not be present. @@ -1522,10 +1554,10 @@ AcceleratorModificationData override_data = ValueToAcceleratorModificationData( accelerator_overrides->front().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - override_data.accelerator); + override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kRemove, override_data.action); // Now re-login to the original profile. @@ -1602,10 +1634,10 @@ AcceleratorModificationData override_data = ValueToAcceleratorModificationData( accelerator_overrides->front().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - override_data.accelerator); + override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kRemove, override_data.action); // Now re-login to the original profile. @@ -1687,10 +1719,10 @@ AcceleratorModificationData override_data = ValueToAcceleratorModificationData( accelerator_overrides->front().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_A, ui::EF_COMMAND_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - override_data.accelerator); + override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kAdd, override_data.action); ExpectAllAcceleratorsEqual(updated_test_data, config_->GetAllAccelerators()); @@ -1752,10 +1784,10 @@ AcceleratorModificationData switch_ime_override_data = ValueToAcceleratorModificationData( switch_to_last_used_ime_overrides->front().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_C, ui::EF_COMMAND_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - switch_ime_override_data.accelerator); + switch_ime_override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kAdd, switch_ime_override_data.action); @@ -1835,10 +1867,10 @@ AcceleratorModificationData switch_ime_override_data = ValueToAcceleratorModificationData( switch_to_last_used_ime_overrides->front().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_C, ui::EF_COMMAND_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - switch_ime_override_data.accelerator); + switch_ime_override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kAdd, switch_ime_override_data.action); @@ -1872,10 +1904,10 @@ AcceleratorModificationData toggle_dictation_data = ValueToAcceleratorModificationData( toggle_dictation_overrides->front().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_C, ui::EF_COMMAND_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - toggle_dictation_data.accelerator); + toggle_dictation_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kAdd, toggle_dictation_data.action); const AcceleratorData expected_test_data_2[] = { @@ -1954,10 +1986,10 @@ AcceleratorModificationData switch_ime_override_data = ValueToAcceleratorModificationData( switch_to_last_used_ime_overrides->front().GetDict()); - CompareAccelerators({/*trigger_on_press=*/true, ui::VKEY_M, - ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, - AcceleratorAction::kSwitchToLastUsedIme}, - switch_ime_override_data.accelerator); + EXPECT_TRUE(CompareAccelerators({/*trigger_on_press=*/true, ui::VKEY_M, + ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, + AcceleratorAction::kSwitchToLastUsedIme}, + switch_ime_override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kAdd, switch_ime_override_data.action); @@ -1993,10 +2025,10 @@ AcceleratorModificationData toggle_dictation_data = ValueToAcceleratorModificationData( toggle_dictation_overrides->front().GetDict()); - CompareAccelerators({/*trigger_on_press=*/true, ui::VKEY_M, - ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, - AcceleratorAction::kEnableOrToggleDictation}, - toggle_dictation_data.accelerator); + EXPECT_TRUE(CompareAccelerators({/*trigger_on_press=*/true, ui::VKEY_M, + ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, + AcceleratorAction::kEnableOrToggleDictation}, + toggle_dictation_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kAdd, toggle_dictation_data.action); const AcceleratorData expected_test_data_2[] = { @@ -2032,10 +2064,10 @@ EXPECT_EQ(1u, switch_to_last_used_ime_overrides_2->size()); switch_ime_override_data = ValueToAcceleratorModificationData( switch_to_last_used_ime_overrides_2->front().GetDict()); - CompareAccelerators({/*trigger_on_press=*/true, ui::VKEY_M, - ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, - AcceleratorAction::kSwitchToLastUsedIme}, - switch_ime_override_data.accelerator); + EXPECT_TRUE(CompareAccelerators({/*trigger_on_press=*/true, ui::VKEY_M, + ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, + AcceleratorAction::kSwitchToLastUsedIme}, + switch_ime_override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kAdd, switch_ime_override_data.action); @@ -2116,10 +2148,10 @@ AcceleratorModificationData override_data = ValueToAcceleratorModificationData( last_used_ime_overrides->front().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - override_data.accelerator); + override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kRemove, override_data.action); // Now verify add pref is present. @@ -2128,9 +2160,10 @@ base::NumberToString(AcceleratorAction::kToggleCalendar)); override_data = ValueToAcceleratorModificationData( toggle_calendar_overrides->front().GetDict()); - CompareAccelerators({/*trigger_on_press=*/true, ui::VKEY_SPACE, - ui::EF_CONTROL_DOWN, AcceleratorAction::kToggleCalendar}, - override_data.accelerator); + EXPECT_TRUE(CompareAccelerators( + {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, + AcceleratorAction::kToggleCalendar}, + override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kAdd, override_data.action); const AcceleratorData updated_test_data[] = { @@ -2197,20 +2230,20 @@ AcceleratorModificationData remove_override_data = ValueToAcceleratorModificationData( last_used_ime_overrides->front().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - remove_override_data.accelerator); + remove_override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kRemove, remove_override_data.action); AcceleratorModificationData add_override_data = ValueToAcceleratorModificationData( last_used_ime_overrides->back().GetDict()); - CompareAccelerators( + EXPECT_TRUE(CompareAccelerators( {/*trigger_on_press=*/true, ui::VKEY_A, ui::EF_COMMAND_DOWN, AcceleratorAction::kSwitchToLastUsedIme}, - add_override_data.accelerator); + add_override_data.accelerator)); EXPECT_EQ(AcceleratorModificationAction::kAdd, add_override_data.action); const AcceleratorData updated_test_data[] = { @@ -2242,4 +2275,86 @@ // Verify pref overrides were loaded correctly. ExpectAllAcceleratorsEqual(updated_test_data, config_->GetAllAccelerators()); } + +TEST_F(AshAcceleratorConfigurationTest, IgnoreBadActionIdPrefs) { + SimulateNewUserFirstLogin(kFakeUserEmail); + const AcceleratorData test_data[] = { + {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, + AcceleratorAction::kSwitchToLastUsedIme}, + {/*trigger_on_press=*/true, ui::VKEY_SPACE, + ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN, + AcceleratorAction::kSwitchToLastUsedIme}, + }; + + config_->Initialize(test_data); + + // Expect that there are no entries stored in the override pref. + const base::Value::Dict& pref_overrides = GetOverridePref(); + EXPECT_TRUE(pref_overrides.empty()); + + // Simulate setting a pref with bad values (invalid action_id). + const ui::Accelerator bad_accelerator(ui::VKEY_B, ui::EF_ALT_DOWN); + SetOverridePref(bad_accelerator, AcceleratorModificationAction::kAdd, + /*action_id*/ 7777777); + + // Simulate login on another account, expect the pref to not be present. + GetSessionControllerClient()->LockScreen(); + SimulateNewUserFirstLogin(kFakeUserEmail2); + const base::Value::Dict& other_user_pref_overrides = GetOverridePref(); + EXPECT_TRUE(other_user_pref_overrides.empty()); + + // Now re-login to the original profile, expect that no prefs are available + // since the bad pref should've been removed. + GetSessionControllerClient()->LockScreen(); + config_->Initialize(test_data); + SimulateUserLogin(kFakeUserEmail); + const base::Value::Dict& original_pref_overrides = GetOverridePref(); + EXPECT_TRUE(original_pref_overrides.empty()); + + const base::Value::Dict& relogin_overrides = GetOverridePref(); + EXPECT_TRUE(relogin_overrides.empty()); + // Verify pref overrides were loaded correctly. + ExpectAllAcceleratorsEqual(test_data, config_->GetAllAccelerators()); +} + +TEST_F(AshAcceleratorConfigurationTest, IgnoreBadAcceleratorPrefs) { + SimulateNewUserFirstLogin(kFakeUserEmail); + const AcceleratorData test_data[] = { + {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, + AcceleratorAction::kSwitchToLastUsedIme}, + {/*trigger_on_press=*/true, ui::VKEY_SPACE, + ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN, + AcceleratorAction::kSwitchToLastUsedIme}, + }; + + config_->Initialize(test_data); + + // Expect that there are no entries stored in the override pref. + const base::Value::Dict& pref_overrides = GetOverridePref(); + EXPECT_TRUE(pref_overrides.empty()); + + // Simulate setting a pref with bad values (invalid action_id). + const ui::Accelerator bad_accelerator(ui::VKEY_B, ui::EF_ALT_DOWN); + SetOverridePref(bad_accelerator, AcceleratorModificationAction::kRemove, + kSwitchToLastUsedIme); + + // Simulate login on another account, expect the pref to not be present. + GetSessionControllerClient()->LockScreen(); + SimulateNewUserFirstLogin(kFakeUserEmail2); + const base::Value::Dict& other_user_pref_overrides = GetOverridePref(); + EXPECT_TRUE(other_user_pref_overrides.empty()); + + // Now re-login to the original profile, expect that no prefs are available + // since the bad pref should've been removed. + GetSessionControllerClient()->LockScreen(); + config_->Initialize(test_data); + SimulateUserLogin(kFakeUserEmail); + const base::Value::Dict& original_pref_overrides = GetOverridePref(); + EXPECT_TRUE(original_pref_overrides.empty()); + + const base::Value::Dict& relogin_overrides = GetOverridePref(); + EXPECT_TRUE(relogin_overrides.empty()); + // Verify pref overrides were loaded correctly. + ExpectAllAcceleratorsEqual(test_data, config_->GetAllAccelerators()); +} } // namespace ash
diff --git a/ash/app_list/quick_app_access_model.cc b/ash/app_list/quick_app_access_model.cc index a639000..6343152 100644 --- a/ash/app_list/quick_app_access_model.cc +++ b/ash/app_list/quick_app_access_model.cc
@@ -93,6 +93,14 @@ return image; } +const std::u16string QuickAppAccessModel::GetAppName() const { + AppListItem* item = GetQuickAppItem(); + if (!item) { + return std::u16string(); + } + return base::UTF8ToUTF16(item->GetDisplayName()); +} + void QuickAppAccessModel::ItemDefaultIconChanged() { if (quick_app_should_show_state_) { // If quick app should already be shown, notify observers when the changed @@ -130,7 +138,7 @@ } } -AppListItem* QuickAppAccessModel::GetQuickAppItem() { +AppListItem* QuickAppAccessModel::GetQuickAppItem() const { return AppListModelProvider::Get()->model()->FindItem(quick_app_id_); }
diff --git a/ash/app_list/quick_app_access_model.h b/ash/app_list/quick_app_access_model.h index 3310fd35..7216e7e 100644 --- a/ash/app_list/quick_app_access_model.h +++ b/ash/app_list/quick_app_access_model.h
@@ -64,6 +64,9 @@ // Returns the quick app's icon as an image, sized to 'icon_size'. gfx::ImageSkia GetAppIcon(gfx::Size icon_size); + // Returns the quick app's display name. + const std::u16string GetAppName() const; + const std::string& quick_app_id() { return quick_app_id_; } bool quick_app_should_show_state() { return quick_app_should_show_state_; } @@ -76,7 +79,7 @@ // AppListControllerObserver: void OnAppListVisibilityChanged(bool shown, int64_t display_id) override; - AppListItem* GetQuickAppItem(); + AppListItem* GetQuickAppItem() const; // Checks if the should show state of the quick app has changed, and notifies // observers when the state does change.
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd index 2b48154..62fd6fd5 100644 --- a/ash/ash_strings.grd +++ b/ash/ash_strings.grd
@@ -6920,6 +6920,21 @@ <message name="IDS_GLANCEABLES_CLASSROOM_ASSIGNMENT_SUBMISSIONS_STATE_ACCESSIBLE_DESCRIPTION" desc="The teacher classroom glanceable shows upcoming/missed/completed assignments from Google Classroom. For each assignment item, this text displays the number of submissions turned in along side the total number of sumbissions. Then this text also displays the number of submissions currently graded."> <ph name="NUM_TURNED_IN">$1<ex>8</ex></ph> of <ph name="TOTAL_NUM_OF_SUBMISSIONS">$2<ex>22</ex></ph> turned in, <ph name="NUM_GRADED">$3<ex>5</ex></ph> graded </message> + <message name="IDS_GLANCEABLES_CLASSROOM_DROPDOWN_ACCESSIBLE_NAME" desc="The glanceable displays classroom items fetched from Google Classroom API. This is the a11y name announced by ChromeVox for the dropdown menu allowing to switch assignments lists: Due soon, No due date, etc."> + Classwork type + </message> + <message name="IDS_GLANCEABLES_CLASSROOM_SELECTED_LIST_ACCESSIBLE_NAME" desc="The glanceable displays classroom items fetched from Google Classroom API. This is the a11y name announced by ChromeVox for the active assignments list: Due soon, No due date, etc."> + Classwork type: <ph name="GLANCEABLES_CLASSROOM_LIST_NAME">$1<ex>Due soon</ex></ph> + </message> + <message name="IDS_GLANCEABLES_TASKS_DROPDOWN_ACCESSIBLE_NAME" desc="The glanceable displays tasks items fetched from Google Tasks API. This is the a11y name announced by ChromeVox for the dropdown menu allowing to switch tasks lists."> + Google tasks list + </message> + <message name="IDS_GLANCEABLES_TASKS_SELECTED_LIST_ACCESSIBLE_NAME" desc="The glanceable displays tasks items fetched from Google Tasks API. This is the a11y name announced by ChromeVox for the active tasks list."> + Google tasks list: <ph name="GLANCEABLES_TASKS_LIST_NAME">$1<ex>Task List 1</ex></ph> + </message> + <message name="IDS_GLANCEABLES_TASKS_SELECTED_LIST_EMPTY_ACCESSIBLE_NAME" desc="The glanceable displays tasks items fetched from Google Tasks API. This is the text announced by ChromeVox in case the selected tasks list is empty."> + Selected list empty, navigate down to add a new task + </message> <!-- Do Not Disturb notification --> <message name="IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TITLE" desc="Label used for the notification that shows up when the 'Do Not Disturb' feature is enabled.">
diff --git a/ash/ash_strings_grd/IDS_GLANCEABLES_CLASSROOM_DROPDOWN_ACCESSIBLE_NAME.png.sha1 b/ash/ash_strings_grd/IDS_GLANCEABLES_CLASSROOM_DROPDOWN_ACCESSIBLE_NAME.png.sha1 new file mode 100644 index 0000000..ecd631c2 --- /dev/null +++ b/ash/ash_strings_grd/IDS_GLANCEABLES_CLASSROOM_DROPDOWN_ACCESSIBLE_NAME.png.sha1
@@ -0,0 +1 @@ +1fd9ba138409241a857f3b346bd22e2b4360df12 \ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_GLANCEABLES_CLASSROOM_SELECTED_LIST_ACCESSIBLE_NAME.png.sha1 b/ash/ash_strings_grd/IDS_GLANCEABLES_CLASSROOM_SELECTED_LIST_ACCESSIBLE_NAME.png.sha1 new file mode 100644 index 0000000..8628e59 --- /dev/null +++ b/ash/ash_strings_grd/IDS_GLANCEABLES_CLASSROOM_SELECTED_LIST_ACCESSIBLE_NAME.png.sha1
@@ -0,0 +1 @@ +365bf287d4aefbee292628f877ea00bfc700c55e \ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_GLANCEABLES_TASKS_DROPDOWN_ACCESSIBLE_NAME.png.sha1 b/ash/ash_strings_grd/IDS_GLANCEABLES_TASKS_DROPDOWN_ACCESSIBLE_NAME.png.sha1 new file mode 100644 index 0000000..7ebe443 --- /dev/null +++ b/ash/ash_strings_grd/IDS_GLANCEABLES_TASKS_DROPDOWN_ACCESSIBLE_NAME.png.sha1
@@ -0,0 +1 @@ +d9ff5705b027c5ecf44f9f250d9a6da6c9a38a3d \ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_GLANCEABLES_TASKS_SELECTED_LIST_ACCESSIBLE_NAME.png.sha1 b/ash/ash_strings_grd/IDS_GLANCEABLES_TASKS_SELECTED_LIST_ACCESSIBLE_NAME.png.sha1 new file mode 100644 index 0000000..d89cd79 --- /dev/null +++ b/ash/ash_strings_grd/IDS_GLANCEABLES_TASKS_SELECTED_LIST_ACCESSIBLE_NAME.png.sha1
@@ -0,0 +1 @@ +81ca666f651516fd7a0f6b6d2600fd5329976ed4 \ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_GLANCEABLES_TASKS_SELECTED_LIST_EMPTY_ACCESSIBLE_NAME.png.sha1 b/ash/ash_strings_grd/IDS_GLANCEABLES_TASKS_SELECTED_LIST_EMPTY_ACCESSIBLE_NAME.png.sha1 new file mode 100644 index 0000000..8e5665f3 --- /dev/null +++ b/ash/ash_strings_grd/IDS_GLANCEABLES_TASKS_SELECTED_LIST_EMPTY_ACCESSIBLE_NAME.png.sha1
@@ -0,0 +1 @@ +17e070bacc4c704fd6a52bede965cb580d88ef00 \ No newline at end of file
diff --git a/ash/frame/non_client_frame_view_ash.cc b/ash/frame/non_client_frame_view_ash.cc index f12537b..51cd0ee 100644 --- a/ash/frame/non_client_frame_view_ash.cc +++ b/ash/frame/non_client_frame_view_ash.cc
@@ -4,33 +4,27 @@ #include "ash/frame/non_client_frame_view_ash.h" -#include <algorithm> #include <memory> -#include <vector> #include "ash/public/cpp/tablet_mode_observer.h" #include "ash/public/cpp/window_properties.h" #include "ash/shell.h" -#include "ash/wm/overview/overview_controller.h" -#include "ash/wm/splitview/split_view_controller.h" #include "ash/wm/tablet_mode/tablet_mode_controller.h" #include "ash/wm/window_state.h" #include "ash/wm/window_state_observer.h" #include "ash/wm/window_util.h" +#include "base/check_op.h" #include "base/functional/bind.h" #include "base/memory/raw_ptr.h" #include "chromeos/ui/base/window_properties.h" #include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h" -#include "chromeos/ui/frame/default_frame_header.h" #include "chromeos/ui/frame/frame_utils.h" #include "chromeos/ui/frame/header_view.h" #include "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h" #include "chromeos/ui/frame/non_client_frame_view_base.h" -#include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" #include "ui/aura/window_observer.h" #include "ui/base/hit_test.h" -#include "ui/base/metadata/metadata_header_macros.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/chromeos/styles/cros_tokens_color_mappings.h" #include "ui/gfx/geometry/rect.h" @@ -174,9 +168,12 @@ } frame_window->SetProperty(kNonClientFrameViewAshKey, this); + window_observation_.Observe(frame_window); header_view_->set_context_menu_controller( frame_context_menu_controller_.get()); + + UpdateWindowRoundedCorners(); } NonClientFrameViewAsh::~NonClientFrameViewAsh() = default; @@ -188,7 +185,8 @@ void NonClientFrameViewAsh::InitImmersiveFullscreenControllerForView( ImmersiveFullscreenController* immersive_fullscreen_controller) { - immersive_fullscreen_controller->Init(header_view_, frame_, header_view_); + immersive_fullscreen_controller->Init(GetHeaderView(), frame_, + GetHeaderView()); } void NonClientFrameViewAsh::SetFrameColors(SkColor active_frame_color, @@ -220,7 +218,7 @@ // will return HTCLIENT so manually check whether `point` lies inside // `header_view_`. gfx::Point point_in_header_coords(screen_coords_point); - views::View::ConvertPointToTarget(this, header_view_, + views::View::ConvertPointToTarget(this, GetHeaderView(), &point_in_header_coords); return header_view_->HitTestRect( gfx::Rect(point_in_header_coords, gfx::Size(1, 1))); @@ -258,6 +256,7 @@ frame_enabled_ = enabled; overlay_view_->SetVisible(frame_enabled_); + UpdateWindowRoundedCorners(); InvalidateLayout(); } @@ -270,6 +269,21 @@ toggle_resize_lock_menu_callback_.Reset(); } +void NonClientFrameViewAsh::OnWindowPropertyChanged(aura::Window* window, + const void* key, + intptr_t old) { + // ChromeOS has rounded frames for certain window states. If these states + // changes, we need to update the rounded corners of the frame associate with + // the `window`accordingly. + if (chromeos::CanPropertyEffectFrameRadius(key)) { + UpdateWindowRoundedCorners(); + } +} + +void NonClientFrameViewAsh::OnWindowDestroying(aura::Window* window) { + window_observation_.Reset(); +} + base::RepeatingCallback<void()> NonClientFrameViewAsh::GetToggleResizeLockMenuCallback() const { return toggle_resize_lock_menu_callback_; @@ -281,7 +295,7 @@ // The HeaderView is not a child of NonClientFrameViewAsh. Redirect the // paint to HeaderView instead. gfx::RectF to_paint(r); - views::View::ConvertRectToTarget(this, header_view_, &to_paint); + views::View::ConvertRectToTarget(this, GetHeaderView(), &to_paint); header_view_->SchedulePaintInRect(gfx::ToEnclosingRect(to_paint)); } }
diff --git a/ash/frame/non_client_frame_view_ash.h b/ash/frame/non_client_frame_view_ash.h index 9b2adbe..b474337 100644 --- a/ash/frame/non_client_frame_view_ash.h +++ b/ash/frame/non_client_frame_view_ash.h
@@ -9,16 +9,14 @@ #include "ash/ash_export.h" #include "ash/frame/frame_context_menu_controller.h" -#include "ash/wm/overview/overview_observer.h" -#include "base/functional/bind.h" #include "base/memory/weak_ptr.h" #include "chromeos/ui/frame/header_view.h" #include "chromeos/ui/frame/highlight_border_overlay.h" #include "chromeos/ui/frame/non_client_frame_view_base.h" #include "third_party/skia/include/core/SkColor.h" +#include "ui/aura/window_observer.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/widget/widget.h" -#include "ui/views/window/non_client_view.h" namespace chromeos { class FrameCaptionButtonContainerView; @@ -41,7 +39,8 @@ // BrowserNonClientFrameViewAsh. class ASH_EXPORT NonClientFrameViewAsh : public chromeos::NonClientFrameViewBase, - public FrameContextMenuController::Delegate { + public FrameContextMenuController::Delegate, + public aura::WindowObserver { public: METADATA_HEADER(NonClientFrameViewAsh); @@ -105,13 +104,18 @@ base::RepeatingCallback<void()> GetToggleResizeLockMenuCallback() const; void ClearToggleResizeLockMenuCallback(); + // aura::WindowObserver: + void OnWindowPropertyChanged(aura::Window* window, + const void* key, + intptr_t old) override; + void OnWindowDestroying(aura::Window* window) override; + protected: // views::View: void OnDidSchedulePaint(const gfx::Rect& r) override; void AddedToWidget() override; private: - friend class NonClientFrameViewAshTestWidgetDelegate; friend class TestWidgetConstraintsDelegate; friend class WindowServiceDelegateImplTest; @@ -130,6 +134,10 @@ std::unique_ptr<FrameContextMenuController> frame_context_menu_controller_; + // Observes property changes to window of `target_widget_`. + base::ScopedObservation<aura::Window, aura::WindowObserver> + window_observation_{this}; + base::RepeatingCallback<void()> toggle_resize_lock_menu_callback_; base::WeakPtrFactory<NonClientFrameViewAsh> weak_factory_{this};
diff --git a/ash/frame/non_client_frame_view_ash_unittest.cc b/ash/frame/non_client_frame_view_ash_unittest.cc index b26c119..02a9bac 100644 --- a/ash/frame/non_client_frame_view_ash_unittest.cc +++ b/ash/frame/non_client_frame_view_ash_unittest.cc
@@ -96,7 +96,7 @@ } chromeos::HeaderView* header_view() const { - return non_client_frame_view_->header_view_; + return non_client_frame_view_->GetHeaderView(); } private:
diff --git a/ash/metrics/user_metrics_recorder.cc b/ash/metrics/user_metrics_recorder.cc index 519f9850..d73d0f1 100644 --- a/ash/metrics/user_metrics_recorder.cc +++ b/ash/metrics/user_metrics_recorder.cc
@@ -136,11 +136,6 @@ if (IsUserInActiveDesktopEnvironment()) { RecordShelfItemCounts(); RecordPeriodicAppListMetrics(); - - base::UmaHistogramBoolean( - "Ash.AppNotificationBadgingPref", - Shell::Get()->session_controller()->GetActivePrefService()->GetBoolean( - prefs::kAppNotificationBadgingEnabled)); } }
diff --git a/ash/metrics/user_metrics_recorder_unittest.cc b/ash/metrics/user_metrics_recorder_unittest.cc index a519b67b..32076dc1 100644 --- a/ash/metrics/user_metrics_recorder_unittest.cc +++ b/ash/metrics/user_metrics_recorder_unittest.cc
@@ -28,8 +28,6 @@ const char kAsh_Shelf_NumberOfUnpinnedItems[] = "Ash.Shelf.NumberOfUnpinnedItems"; -const char kAsh_NotificationBadgeShownPref[] = "Ash.AppNotificationBadgingPref"; - } // namespace // Test fixture for the UserMetricsRecorder class. The tests manage their own @@ -100,7 +98,6 @@ histograms().ExpectTotalCount(kAsh_Shelf_NumberOfItems, 0); histograms().ExpectTotalCount(kAsh_Shelf_NumberOfPinnedItems, 0); histograms().ExpectTotalCount(kAsh_Shelf_NumberOfUnpinnedItems, 0); - histograms().ExpectTotalCount(kAsh_NotificationBadgeShownPref, 0); } // Verifies that the IsUserInActiveDesktopEnvironment() dependent stats are @@ -114,7 +111,6 @@ histograms().ExpectTotalCount(kAsh_Shelf_NumberOfItems, 1); histograms().ExpectTotalCount(kAsh_Shelf_NumberOfPinnedItems, 1); histograms().ExpectTotalCount(kAsh_Shelf_NumberOfUnpinnedItems, 1); - histograms().ExpectTotalCount(kAsh_NotificationBadgeShownPref, 1); } // Verify the shelf item counts recorded by the
diff --git a/ash/shelf/drag_window_from_shelf_controller_unittest.cc b/ash/shelf/drag_window_from_shelf_controller_unittest.cc index 2bdbe92d..3e6004a 100644 --- a/ash/shelf/drag_window_from_shelf_controller_unittest.cc +++ b/ash/shelf/drag_window_from_shelf_controller_unittest.cc
@@ -490,9 +490,18 @@ EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized()); } +// TODO(crbug.com/1473400): Re-enable the test once the bug is fixed. +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) +#define MAYBE_VerifyHomeLauncherAnimationMetrics \ + DISABLED_VerifyHomeLauncherAnimationMetrics +#else +#define MAYBE_VerifyHomeLauncherAnimationMetrics \ + VerifyHomeLauncherAnimationMetrics +#endif // Verify that metrics of home launcher animation are recorded correctly when // swiping up from shelf with sufficient velocity. -TEST_F(DragWindowFromShelfControllerTest, VerifyHomeLauncherAnimationMetrics) { +TEST_F(DragWindowFromShelfControllerTest, + MAYBE_VerifyHomeLauncherAnimationMetrics) { // Set non-zero animation duration to report animation metrics. ui::ScopedAnimationDurationScaleMode non_zero_duration_mode( ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
diff --git a/ash/shelf/home_button.cc b/ash/shelf/home_button.cc index 25463e9e..feb023c 100644 --- a/ash/shelf/home_button.cc +++ b/ash/shelf/home_button.cc
@@ -701,8 +701,8 @@ quick_app_button_ = expandable_container_->AddChildView( std::make_unique<views::ImageButton>(base::BindRepeating( &HomeButton::QuickAppButtonPressed, base::Unretained(this)))); - // TODO(b/266734005): Replace with localized string once finalized. - quick_app_button_->SetAccessibleName(u"QuickApp"); + quick_app_button_->SetAccessibleName( + AppListModelProvider::Get()->quick_app_access_model()->GetAppName()); const int control_size = ShelfControlButton::CalculatePreferredSize().width();
diff --git a/ash/system/media/media_tray.cc b/ash/system/media/media_tray.cc index 21243203..dfccd24 100644 --- a/ash/system/media/media_tray.cc +++ b/ash/system/media/media_tray.cc
@@ -191,22 +191,30 @@ base::BindRepeating(&PinButton::ButtonPressed, base::Unretained(this)), IconButton::Type::kMedium, - MediaTray::IsPinnedToShelf() ? &kPinnedIcon : &kUnpinnedIcon, + &kUnpinnedIcon, MediaTray::IsPinnedToShelf() ? IDS_ASH_GLOBAL_MEDIA_CONTROLS_PINNED_BUTTON_TOOLTIP_TEXT - : IDS_ASH_GLOBAL_MEDIA_CONTROLS_UNPINNED_BUTTON_TOOLTIP_TEXT) {} + : IDS_ASH_GLOBAL_MEDIA_CONTROLS_UNPINNED_BUTTON_TOOLTIP_TEXT, + /*is_togglable=*/true, + /*has_border=*/false) { + SetIconSize(kTrayTopShortcutButtonIconSize); + SetToggledVectorIcon(kPinnedIcon); + if (chromeos::features::IsJellyEnabled()) { + SetIconColorId(cros_tokens::kCrosSysOnSurface); + SetBackgroundToggledColorId(cros_tokens::kCrosSysSystemPrimaryContainer); + } else { + SetIconColor(AshColorProvider::Get()->GetContentLayerColor( + AshColorProvider::ContentLayerType::kIconColorPrimary)); + } + SetToggled(MediaTray::IsPinnedToShelf()); +} void MediaTray::PinButton::ButtonPressed() { MediaTray::SetPinnedToShelf(!MediaTray::IsPinnedToShelf()); base::UmaHistogramBoolean("Media.CrosGlobalMediaControls.PinAction", MediaTray::IsPinnedToShelf()); - SetImage(views::Button::STATE_NORMAL, - CreateVectorIcon( - MediaTray::IsPinnedToShelf() ? kPinnedIcon : kUnpinnedIcon, - kTrayTopShortcutButtonIconSize, - AshColorProvider::Get()->GetContentLayerColor( - AshColorProvider::ContentLayerType::kIconColorPrimary))); + SetToggled(MediaTray::IsPinnedToShelf()); SetTooltipText(l10n_util::GetStringUTF16( MediaTray::IsPinnedToShelf() ? IDS_ASH_GLOBAL_MEDIA_CONTROLS_PINNED_BUTTON_TOOLTIP_TEXT
diff --git a/ash/system/message_center/ash_notification_view_pixeltest.cc b/ash/system/message_center/ash_notification_view_pixeltest.cc index 97c8bca9..00ecbc74 100644 --- a/ash/system/message_center/ash_notification_view_pixeltest.cc +++ b/ash/system/message_center/ash_notification_view_pixeltest.cc
@@ -319,7 +319,7 @@ EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen( base::StrCat({"screen_capture_popup_notification_", GetDisplayTypeName(GetDisplayType())}), - /*revision_number=*/5, + /*revision_number=*/6, test_api()->GetPopupViewForId(kScreenCaptureNotificationId))); }
diff --git a/ash/system/unified/classroom_bubble_base_view.cc b/ash/system/unified/classroom_bubble_base_view.cc index b006ef51..1e802b3 100644 --- a/ash/system/unified/classroom_bubble_base_view.cc +++ b/ash/system/unified/classroom_bubble_base_view.cc
@@ -79,8 +79,8 @@ combo_box_view_->SetID( base::to_underlying(GlanceablesViewId::kClassroomBubbleComboBox)); combo_box_view_->SetSelectedIndex(0); - // TODO(b/294681832): Finalize, and then localize strings. - combo_box_view_->SetTooltipTextAndAccessibleName(u"Classwork type"); + combo_box_view_->SetTooltipTextAndAccessibleName(l10n_util::GetStringUTF16( + IDS_GLANCEABLES_CLASSROOM_DROPDOWN_ACCESSIBLE_NAME)); combo_box_view_->SetAccessibleDescription(u""); combobox_view_observation_.Observe(combo_box_view_); @@ -158,8 +158,8 @@ empty_list_label_->SetVisible(is_list_empty); list_footer_view_->SetVisible(!is_list_empty); - // TODO(b/294681832): Finalize, and then localize strings. - list_container_view_->SetAccessibleName(u"Classwork " + list_name); + list_container_view_->SetAccessibleName(l10n_util::GetStringFUTF16( + IDS_GLANCEABLES_CLASSROOM_SELECTED_LIST_ACCESSIBLE_NAME, list_name)); list_container_view_->SetAccessibleDescription( list_footer_view_->items_count_label()); list_container_view_->NotifyAccessibilityEvent(
diff --git a/ash/system/unified/tasks_bubble_view.cc b/ash/system/unified/tasks_bubble_view.cc index eae6fb7..eef85fbd 100644 --- a/ash/system/unified/tasks_bubble_view.cc +++ b/ash/system/unified/tasks_bubble_view.cc
@@ -20,6 +20,7 @@ #include "ash/style/ash_color_id.h" #include "ash/system/unified/glanceable_tray_child_bubble.h" #include "ash/system/unified/tasks_combobox_model.h" +#include "base/strings/utf_string_conversions.h" #include "base/types/cxx23_to_underlying.h" #include "chromeos/constants/chromeos_features.h" #include "ui/base/l10n/l10n_util.h" @@ -155,9 +156,9 @@ task_list_combo_box_view_->SetSizeToLargestLabel(false); combobox_view_observation_.Observe(task_list_combo_box_view_); - // TODO(b/294681832): Finalize, and then localize strings. task_list_combo_box_view_->SetTooltipTextAndAccessibleName( - u"Google tasks list"); + l10n_util::GetStringUTF16( + IDS_GLANCEABLES_TASKS_DROPDOWN_ACCESSIBLE_NAME)); task_list_combo_box_view_->SetAccessibleDescription(u""); task_list_combo_box_view_->SetCallback(base::BindRepeating( &TasksBubbleView::SelectedTasksListChanged, base::Unretained(this))); @@ -234,9 +235,9 @@ list_footer_view_->UpdateItemsCount(num_tasks_shown_, num_tasks_); list_footer_view_->SetVisible(num_tasks_shown_ > 0); - // TODO(b/294681832): Finalize, and then localize strings. - task_items_container_view_->SetAccessibleName(base::UTF8ToUTF16( - base::StringPrintf("Tasks list: %s", task_list_title.c_str()))); + task_items_container_view_->SetAccessibleName(l10n_util::GetStringFUTF16( + IDS_GLANCEABLES_TASKS_SELECTED_LIST_ACCESSIBLE_NAME, + base::UTF8ToUTF16(task_list_title))); task_items_container_view_->SetAccessibleDescription( list_footer_view_->items_count_label()); task_items_container_view_->NotifyAccessibilityEvent( @@ -252,9 +253,9 @@ void TasksBubbleView::AnnounceListStateOnComboBoxAccessibility() { if (add_new_task_button_->GetVisible()) { - // TODO(b/294681832): Finalize, and then localize strings. task_list_combo_box_view_->GetViewAccessibility().AnnounceText( - u"Selected list empty, navigate down to add a new task"); + l10n_util::GetStringUTF16( + IDS_GLANCEABLES_TASKS_SELECTED_LIST_EMPTY_ACCESSIBLE_NAME)); } else if (list_footer_view_->items_count_label()->GetVisible()) { task_list_combo_box_view_->GetViewAccessibility().AnnounceText( list_footer_view_->items_count_label()->GetText());
diff --git a/ash/system/video_conference/bubble/bubble_view.cc b/ash/system/video_conference/bubble/bubble_view.cc index dc99336..58370b90 100644 --- a/ash/system/video_conference/bubble/bubble_view.cc +++ b/ash/system/video_conference/bubble/bubble_view.cc
@@ -152,7 +152,6 @@ views::BoxLayout::CrossAxisAlignment::kStretch); scroll_contents_view->SetInsideBorderInsets( gfx::Insets::VH(16, kVideoConferenceBubbleHorizontalPadding)); - scroll_contents_view->SetBetweenChildSpacing(16); // Make the effects sections children of the `views::FlexLayoutView`, so that // they scroll (if more effects are present than can fit in the available
diff --git a/ash/system/video_conference/bubble/bubble_view_pixeltest.cc b/ash/system/video_conference/bubble/bubble_view_pixeltest.cc index ee9ea36..6edb0bd 100644 --- a/ash/system/video_conference/bubble/bubble_view_pixeltest.cc +++ b/ash/system/video_conference/bubble/bubble_view_pixeltest.cc
@@ -78,6 +78,7 @@ cat_ears_ = std::make_unique<fake_video_conference::CatEarsEffect>(); long_text_effect_ = std::make_unique< fake_video_conference::FakeLongTextLabelToggleEffect>(); + shaggy_fur_ = std::make_unique<fake_video_conference::ShaggyFurEffect>(); AshTestBase::SetUp(); @@ -87,6 +88,7 @@ void TearDown() override { AshTestBase::TearDown(); + shaggy_fur_.reset(); long_text_effect_.reset(); cat_ears_.reset(); office_bunny_.reset(); @@ -154,6 +156,10 @@ return long_text_effect_.get(); } + fake_video_conference::ShaggyFurEffect* shaggy_fur() { + return shaggy_fur_.get(); + } + private: base::test::ScopedFeatureList scoped_feature_list_; std::unique_ptr<FakeVideoConferenceTrayController> controller_; @@ -161,8 +167,33 @@ std::unique_ptr<fake_video_conference::CatEarsEffect> cat_ears_; std::unique_ptr<fake_video_conference::FakeLongTextLabelToggleEffect> long_text_effect_; + std::unique_ptr<fake_video_conference::ShaggyFurEffect> shaggy_fur_; }; +// Captures the basic bubble view with one media app, 2 toggle effects and 1 set +// value effects. +TEST_F(BubbleViewPixelTest, Basic) { + controller()->ClearMediaApps(); + controller()->AddMediaApp(CreateFakeMediaApp( + /*is_capturing_camera=*/true, /*is_capturing_microphone=*/false, + /*is_capturing_screen=*/false, /*title=*/u"Meet", + /*url=*/kMeetTestUrl)); + + // Add 2 toggle effects. + controller()->effects_manager().RegisterDelegate(office_bunny()); + controller()->effects_manager().RegisterDelegate(long_text_effect()); + + // Add one set-value effect. + controller()->effects_manager().RegisterDelegate(shaggy_fur()); + + LeftClickOn(video_conference_tray()->GetToggleBubbleButtonForTest()); + ASSERT_TRUE(bubble_view()); + + EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen( + "video_conference_bubble_view_basic", + /*revision_number=*/0, bubble_view())); +} + // Pixel test that tests toggled on/off and focused/not focused for the toggle // effect button. TEST_F(BubbleViewPixelTest, ToggleButton) { @@ -233,7 +264,7 @@ EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen( "video_conference_tray_return_to_app_one_app", - /*revision_number=*/2, GetReturnToAppPanel())); + /*revision_number=*/3, GetReturnToAppPanel())); controller()->AddMediaApp(CreateFakeMediaApp( /*is_capturing_camera=*/false, /*is_capturing_microphone=*/true, @@ -249,7 +280,7 @@ EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen( "video_conference_tray_return_to_app_two_apps_collapsed", - /*revision_number=*/2, return_to_app_panel)); + /*revision_number=*/3, return_to_app_panel)); // Click the summary row to expand the panel. auto* summary_row = static_cast<video_conference::ReturnToAppButton*>( @@ -259,7 +290,7 @@ EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen( "video_conference_tray_return_to_app_two_apps_expanded", - /*revision_number=*/2, return_to_app_panel)); + /*revision_number=*/3, return_to_app_panel)); } TEST_F(BubbleViewPixelTest, ReturnToAppLinux) {
diff --git a/ash/webui/personalization_app/resources/js/ambient/ambient_preview_base.ts b/ash/webui/personalization_app/resources/js/ambient/ambient_preview_base.ts index 100efbc7..8edaf1c 100644 --- a/ash/webui/personalization_app/resources/js/ambient/ambient_preview_base.ts +++ b/ash/webui/personalization_app/resources/js/ambient/ambient_preview_base.ts
@@ -102,9 +102,11 @@ } private computeLoading_(): boolean { - return this.isAmbientModeAllowed_ && - (this.ambientModeEnabled_ === null || this.albums_ === null || - this.topicSource_ === null || this.previewImages_ === null); + if (!this.isAmbientModeAllowed_ || this.ambientModeEnabled_ === false) { + return false; + } + return this.ambientModeEnabled_ === null || this.albums_ === null || + this.topicSource_ === null || this.previewImages_ === null; } private onLoadingChanged_(value: boolean) {
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_row.html b/ash/webui/shortcut_customization_ui/resources/js/accelerator_row.html index 4dab6a62..6e73a2cf 100644 --- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_row.html +++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_row.html
@@ -80,7 +80,8 @@ } </style> -<tr id="container" tabindex$="[[getTabIndex()]]"> +<tr id="container" tabindex$="[[getTabIndex()]]" on-focus="onRowFocused" + on-blur="onRowBlur"> <th id="descriptionText" scope="row">[[description]]</th> <td> <template is="dom-if" if="[[isDefaultLayout(layoutStyle)]]"> @@ -88,7 +89,8 @@ index-as="index"> <accelerator-view class="accelerator-item" accelerator-info="[[item]]" action="[[action]]" source="[[source]]" source-is-locked="[[isLocked]]" - show-edit-icon="true" is-first-accelerator="[[isFirstAccelerator(index)]]"> + show-edit-icon="true" is-first-accelerator="[[isFirstAccelerator(index)]]" + highlighted="[[selected]]"> </accelerator-view> </template> <div id="noShortcutAssignedContainer" hidden="[[!isEmptyList(acceleratorInfos)]]"> @@ -105,7 +107,7 @@ </template> <template is="dom-if" if="[[isTextLayout(layoutStyle)]]"> <text-accelerator parts="[[getTextAcceleratorParts(acceleratorInfos)]]" - action="[[action]]" source="[[source]]"> + action="[[action]]" source="[[source]]" highlighted="[[selected]]"> </text-accelerator> </template> </td>
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_row.ts b/ash/webui/shortcut_customization_ui/resources/js/accelerator_row.ts index e924e21..202a8c6 100644 --- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_row.ts +++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_row.ts
@@ -76,6 +76,11 @@ value: 0, observer: AcceleratorRowElement.prototype.onSourceChanged, }, + + selected: { + type: Boolean, + reflectToAttribute: true, + }, }; } @@ -84,6 +89,7 @@ layoutStyle: LayoutStyle; action: number; source: AcceleratorSource; + selected: boolean; private isLocked: boolean; private shortcutInterfaceProvider: ShortcutProviderInterface = getShortcutProvider(); @@ -157,6 +163,14 @@ return isCustomizationDisabled() ? -1 : 0; } + protected onRowFocused(): void { + this.selected = true; + } + + protected onRowBlur(): void { + this.selected = false; + } + static get template(): HTMLTemplateElement { return getTemplate(); }
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html index 2378314e..22869074 100644 --- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html +++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html
@@ -37,11 +37,14 @@ <template is="dom-if" if="[[!showEditView(viewState)]]"> <div id="accelerator-keys"> <template is="dom-repeat" items="[[modifiers]]"> - <input-key key="[[item]]" key-state="modifier-selected"></input-key> + <input-key key="[[item]]" key-state="modifier-selected" + highlighted="[[highlighted]]"> + </input-key> </template> <input-key key="[[acceleratorInfo.layoutProperties.standardAccelerator.keyDisplay]]" - key-state="alpha-numeric-selected"> + key-state="alpha-numeric-selected" + highlighted="[[highlighted]]"> </input-key> </div> <div class="lock-icon-container"
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts index 46db152..8e482b5 100644 --- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts +++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts
@@ -134,6 +134,11 @@ computed: 'computeIsDisabled(acceleratorInfo.*)', reflectToAttribute: true, }, + + highlighted: { + type: Boolean, + reflectToAttribute: true, + }, }; } @@ -148,6 +153,7 @@ categoryIsLocked: boolean; isFirstAccelerator: boolean; isDisabled: boolean; + highlighted: boolean; protected pendingAcceleratorInfo: StandardAcceleratorInfo; protected isCapturing: boolean; private modifiers: string[];
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc index 81fd00b..021f0a7 100644 --- a/ash/wm/overview/overview_controller.cc +++ b/ash/wm/overview/overview_controller.cc
@@ -34,6 +34,8 @@ #include "base/task/single_thread_task_runner.h" #include "base/trace_event/trace_event.h" #include "chromeos/constants/chromeos_features.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/presentation_time_recorder.h" #include "ui/wm/core/window_util.h" #include "ui/wm/public/activation_client.h" @@ -52,6 +54,8 @@ constexpr base::TimeDelta kOcclusionPauseDurationForEnd = base::Milliseconds(500); +constexpr base::TimeDelta kEnterExitPresentationMaxLatency = base::Seconds(2); + bool IsSplitViewDividerDraggedOrAnimated() { SplitViewController* split_view_controller = SplitViewController::Get(Shell::GetPrimaryRootWindow()); @@ -336,6 +340,12 @@ TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("ui", "OverviewController::ExitOverview", this); + auto presentation_time_recorder = CreatePresentationTimeHistogramRecorder( + Shell::GetPrimaryRootWindow()->layer()->GetCompositor(), + kExitOverviewPresentationHistogram, "", + kEnterExitPresentationMaxLatency); + presentation_time_recorder->RequestNext(); + // Suspend occlusion tracker until the exit animation is complete. PauseOcclusionTracker(); @@ -395,6 +405,13 @@ DCHECK(CanEnterOverview()); TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("ui", "OverviewController::EnterOverview", this); + + auto presentation_time_recorder = CreatePresentationTimeHistogramRecorder( + Shell::GetPrimaryRootWindow()->layer()->GetCompositor(), + kEnterOverviewPresentationHistogram, "", + kEnterExitPresentationMaxLatency); + presentation_time_recorder->RequestNext(); + if (auto* active_window = window_util::GetActiveWindow(); active_window) { auto* active_widget = views::Widget::GetWidgetForNativeView(active_window);
diff --git a/ash/wm/overview/overview_metrics.h b/ash/wm/overview/overview_metrics.h index b51b0dc..8927fe4a 100644 --- a/ash/wm/overview/overview_metrics.h +++ b/ash/wm/overview/overview_metrics.h
@@ -55,6 +55,11 @@ }; void RecordOverviewEndAction(OverviewEndAction type); +inline constexpr char kEnterOverviewPresentationHistogram[] = + "Ash.Overview.Enter.PresentationTime"; +inline constexpr char kExitOverviewPresentationHistogram[] = + "Ash.Overview.Exit.PresentationTime"; + } // namespace ash #endif // ASH_WM_OVERVIEW_OVERVIEW_METRICS_H_
diff --git a/ash/wm/window_cycle/window_cycle_list.cc b/ash/wm/window_cycle/window_cycle_list.cc index 6be4988..49e5624 100644 --- a/ash/wm/window_cycle/window_cycle_list.cc +++ b/ash/wm/window_cycle/window_cycle_list.cc
@@ -26,7 +26,9 @@ #include "ui/aura/scoped_window_targeter.h" #include "ui/aura/window.h" #include "ui/aura/window_targeter.h" +#include "ui/compositor/layer.h" #include "ui/compositor/layer_type.h" +#include "ui/compositor/presentation_time_recorder.h" #include "ui/display/display.h" #include "ui/events/event.h" #include "ui/views/widget/widget.h" @@ -40,6 +42,11 @@ constexpr char kSameAppWindowCycleSkippedWindowsHistogramName[] = "Ash.WindowCycleController.SameApp.SkippedWindows"; +constexpr char kEnterWindowCyclePresentationHistogramName[] = + "Ash.WindowCycleController.Enter.PresentationTime"; + +constexpr base::TimeDelta kEnterPresentationMaxLatency = base::Seconds(2); + bool g_disable_initial_delay = false; // Delay before the UI fade in animation starts. This is so users can switch @@ -332,8 +339,17 @@ void WindowCycleList::InitWindowCycleView() { if (cycle_view_) return; + + TRACE_EVENT0("ui", "WindowCycleList::InitWindowCycleView"); + aura::Window* root_window = Shell::GetRootWindowForNewWindows(); + auto presentation_time_recorder = CreatePresentationTimeHistogramRecorder( + root_window->layer()->GetCompositor(), + kEnterWindowCyclePresentationHistogramName, "", + kEnterPresentationMaxLatency); + presentation_time_recorder->RequestNext(); + // Close any tray bubbles that are opened before creating the cycle view. StatusAreaWidget* status_area_widget = RootWindowController::ForWindow(root_window)->GetStatusAreaWidget();
diff --git a/base/BUILD.gn b/base/BUILD.gn index 49c1454f..33d5d29 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn
@@ -1953,11 +1953,20 @@ "apple/call_with_eh_frame_asm.S", "apple/dispatch_source_mach.cc", "apple/dispatch_source_mach.h", + "apple/mach_logging.cc", + "apple/mach_logging.h", "apple/osstatus_logging.h", "apple/osstatus_logging.mm", "apple/owned_objc.h", "apple/owned_objc.mm", "apple/scoped_cffiledescriptorref.h", + "apple/scoped_dispatch_object.h", + "apple/scoped_mach_port.cc", + "apple/scoped_mach_port.h", + "apple/scoped_mach_vm.cc", + "apple/scoped_mach_vm.h", + "apple/scoped_nsautorelease_pool.cc", + "apple/scoped_nsautorelease_pool.h", "apple/scoped_nsobject.h", "apple/scoped_objc_class_swizzler.h", "apple/scoped_objc_class_swizzler.mm", @@ -1967,14 +1976,6 @@ "files/file_util_apple.mm", "mac/foundation_util.h", "mac/foundation_util.mm", - "mac/mach_logging.cc", - "mac/mach_logging.h", - "mac/scoped_mach_port.cc", - "mac/scoped_mach_port.h", - "mac/scoped_mach_vm.cc", - "mac/scoped_mach_vm.h", - "mac/scoped_nsautorelease_pool.cc", - "mac/scoped_nsautorelease_pool.h", "memory/platform_shared_memory_mapper_apple.cc", "memory/platform_shared_memory_region_apple.cc", "message_loop/message_pump_apple.h", @@ -2025,7 +2026,6 @@ "mac/scoped_authorizationref.h", "mac/scoped_authorizationref.mm", "mac/scoped_cftyperef.h", - "mac/scoped_dispatch_object.h", "mac/scoped_ionotificationportref.h", "mac/scoped_ioobject.h", "mac/scoped_ioplugininterface.h", @@ -3729,12 +3729,12 @@ "allocator/partition_allocator/shim/allocator_interception_mac_unittest.mm", "allocator/partition_allocator/shim/malloc_zone_functions_mac_unittest.cc", "apple/call_with_eh_frame_unittest.mm", + "apple/scoped_mach_vm_unittest.cc", "apple/scoped_objc_class_swizzler_unittest.mm", "enterprise_util_mac_unittest.mm", "mac/launch_application_unittest.mm", "mac/mac_util_unittest.mm", "mac/mach_port_rendezvous_unittest.cc", - "mac/scoped_mach_vm_unittest.cc", "mac/scoped_sending_event_unittest.mm", "message_loop/message_pump_apple_unittest.mm", "power_monitor/thermal_state_observer_mac_unittest.mm",
diff --git a/base/apple/dispatch_source_mach.cc b/base/apple/dispatch_source_mach.cc index ea61be7..8b1bf023 100644 --- a/base/apple/dispatch_source_mach.cc +++ b/base/apple/dispatch_source_mach.cc
@@ -4,7 +4,7 @@ #include "base/apple/dispatch_source_mach.h" -#include "base/mac/scoped_dispatch_object.h" +#include "base/apple/scoped_dispatch_object.h" namespace base {
diff --git a/base/apple/dispatch_source_mach_unittest.cc b/base/apple/dispatch_source_mach_unittest.cc index 128028a..7cc54b1 100644 --- a/base/apple/dispatch_source_mach_unittest.cc +++ b/base/apple/dispatch_source_mach_unittest.cc
@@ -8,8 +8,8 @@ #include <memory> +#include "base/apple/scoped_mach_port.h" #include "base/logging.h" -#include "base/mac/scoped_mach_port.h" #include "base/test/test_timeouts.h" #include "testing/gtest/include/gtest/gtest.h" @@ -38,8 +38,8 @@ } private: - base::mac::ScopedMachReceiveRight receive_right_; - base::mac::ScopedMachSendRight send_right_; + base::apple::ScopedMachReceiveRight receive_right_; + base::apple::ScopedMachSendRight send_right_; }; TEST_F(DispatchSourceMachTest, ReceiveAfterResume) {
diff --git a/base/mac/mach_logging.cc b/base/apple/mach_logging.cc similarity index 87% rename from base/mac/mach_logging.cc rename to base/apple/mach_logging.cc index d792370..f91b48c 100644 --- a/base/mac/mach_logging.cc +++ b/base/apple/mach_logging.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 "base/mac/mach_logging.h" +#include "base/apple/mach_logging.h" #include <iomanip> #include <string> @@ -38,13 +38,10 @@ int line, LogSeverity severity, mach_error_t mach_err) - : LogMessage(file_path, line, severity), - mach_err_(mach_err) { -} + : LogMessage(file_path, line, severity), mach_err_(mach_err) {} MachLogMessage::~MachLogMessage() { - stream() << ": " - << mach_error_string(mach_err_) + stream() << ": " << mach_error_string(mach_err_) << FormatMachErrorNumber(mach_err_); } @@ -54,13 +51,10 @@ int line, LogSeverity severity, kern_return_t bootstrap_err) - : LogMessage(file_path, line, severity), - bootstrap_err_(bootstrap_err) { -} + : LogMessage(file_path, line, severity), bootstrap_err_(bootstrap_err) {} BootstrapLogMessage::~BootstrapLogMessage() { - stream() << ": " - << bootstrap_strerror(bootstrap_err_); + stream() << ": " << bootstrap_strerror(bootstrap_err_); switch (bootstrap_err_) { case BOOTSTRAP_SUCCESS:
diff --git a/base/apple/mach_logging.h b/base/apple/mach_logging.h new file mode 100644 index 0000000..d6593ef --- /dev/null +++ b/base/apple/mach_logging.h
@@ -0,0 +1,171 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_APPLE_MACH_LOGGING_H_ +#define BASE_APPLE_MACH_LOGGING_H_ + +#include <mach/mach.h> + +#include "base/base_export.h" +#include "base/logging.h" +#include "build/blink_buildflags.h" +#include "build/build_config.h" + +// Use the MACH_LOG family of macros along with a mach_error_t (kern_return_t) +// containing a Mach error. The error value will be decoded so that logged +// messages explain the error. +// +// Use the BOOTSTRAP_LOG family of macros specifically for errors that occur +// while interoperating with the bootstrap subsystem. These errors will first +// be looked up as bootstrap error messages. If no match is found, they will +// be treated as generic Mach errors, as in MACH_LOG. +// +// Examples: +// +// kern_return_t kr = mach_timebase_info(&info); +// if (kr != KERN_SUCCESS) { +// MACH_LOG(ERROR, kr) << "mach_timebase_info"; +// } +// +// kr = vm_deallocate(task, address, size); +// MACH_DCHECK(kr == KERN_SUCCESS, kr) << "vm_deallocate"; + +namespace logging { + +class BASE_EXPORT MachLogMessage : public logging::LogMessage { + public: + MachLogMessage(const char* file_path, + int line, + LogSeverity severity, + mach_error_t mach_err); + + MachLogMessage(const MachLogMessage&) = delete; + MachLogMessage& operator=(const MachLogMessage&) = delete; + + ~MachLogMessage() override; + + private: + mach_error_t mach_err_; +}; + +} // namespace logging + +#if DCHECK_IS_ON() +#define MACH_DVLOG_IS_ON(verbose_level) VLOG_IS_ON(verbose_level) +#else +#define MACH_DVLOG_IS_ON(verbose_level) 0 +#endif + +#define MACH_LOG_STREAM(severity, mach_err) \ + COMPACT_GOOGLE_LOG_EX_ ## severity(MachLogMessage, mach_err).stream() +#define MACH_VLOG_STREAM(verbose_level, mach_err) \ + logging::MachLogMessage(__FILE__, __LINE__, \ + -verbose_level, mach_err).stream() + +#define MACH_LOG(severity, mach_err) \ + LAZY_STREAM(MACH_LOG_STREAM(severity, mach_err), LOG_IS_ON(severity)) +#define MACH_LOG_IF(severity, condition, mach_err) \ + LAZY_STREAM(MACH_LOG_STREAM(severity, mach_err), \ + LOG_IS_ON(severity) && (condition)) + +#define MACH_VLOG(verbose_level, mach_err) \ + LAZY_STREAM(MACH_VLOG_STREAM(verbose_level, mach_err), \ + VLOG_IS_ON(verbose_level)) +#define MACH_VLOG_IF(verbose_level, condition, mach_err) \ + LAZY_STREAM(MACH_VLOG_STREAM(verbose_level, mach_err), \ + VLOG_IS_ON(verbose_level) && (condition)) + +#define MACH_CHECK(condition, mach_err) \ + LAZY_STREAM(MACH_LOG_STREAM(FATAL, mach_err), !(condition)) \ + << "Check failed: " # condition << ". " + +#define MACH_DLOG(severity, mach_err) \ + LAZY_STREAM(MACH_LOG_STREAM(severity, mach_err), DLOG_IS_ON(severity)) +#define MACH_DLOG_IF(severity, condition, mach_err) \ + LAZY_STREAM(MACH_LOG_STREAM(severity, mach_err), \ + DLOG_IS_ON(severity) && (condition)) + +#define MACH_DVLOG(verbose_level, mach_err) \ + LAZY_STREAM(MACH_VLOG_STREAM(verbose_level, mach_err), \ + MACH_DVLOG_IS_ON(verbose_level)) +#define MACH_DVLOG_IF(verbose_level, condition, mach_err) \ + LAZY_STREAM(MACH_VLOG_STREAM(verbose_level, mach_err), \ + MACH_DVLOG_IS_ON(verbose_level) && (condition)) + +#define MACH_DCHECK(condition, mach_err) \ + LAZY_STREAM(MACH_LOG_STREAM(FATAL, mach_err), \ + DCHECK_IS_ON() && !(condition)) \ + << "Check failed: " #condition << ". " + +#if BUILDFLAG(USE_BLINK) + +namespace logging { + +class BASE_EXPORT BootstrapLogMessage : public logging::LogMessage { + public: + BootstrapLogMessage(const char* file_path, + int line, + LogSeverity severity, + kern_return_t bootstrap_err); + + BootstrapLogMessage(const BootstrapLogMessage&) = delete; + BootstrapLogMessage& operator=(const BootstrapLogMessage&) = delete; + + ~BootstrapLogMessage() override; + + private: + kern_return_t bootstrap_err_; +}; + +} // namespace logging + +#define BOOTSTRAP_DVLOG_IS_ON MACH_DVLOG_IS_ON + +#define BOOTSTRAP_LOG_STREAM(severity, bootstrap_err) \ + COMPACT_GOOGLE_LOG_EX_ ## severity(BootstrapLogMessage, \ + bootstrap_err).stream() +#define BOOTSTRAP_VLOG_STREAM(verbose_level, bootstrap_err) \ + logging::BootstrapLogMessage(__FILE__, __LINE__, \ + -verbose_level, bootstrap_err).stream() + +#define BOOTSTRAP_LOG(severity, bootstrap_err) \ + LAZY_STREAM(BOOTSTRAP_LOG_STREAM(severity, \ + bootstrap_err), LOG_IS_ON(severity)) +#define BOOTSTRAP_LOG_IF(severity, condition, bootstrap_err) \ + LAZY_STREAM(BOOTSTRAP_LOG_STREAM(severity, bootstrap_err), \ + LOG_IS_ON(severity) && (condition)) + +#define BOOTSTRAP_VLOG(verbose_level, bootstrap_err) \ + LAZY_STREAM(BOOTSTRAP_VLOG_STREAM(verbose_level, bootstrap_err), \ + VLOG_IS_ON(verbose_level)) +#define BOOTSTRAP_VLOG_IF(verbose_level, condition, bootstrap_err) \ + LAZY_STREAM(BOOTSTRAP_VLOG_STREAM(verbose_level, bootstrap_err), \ + VLOG_IS_ON(verbose_level) && (condition)) + +#define BOOTSTRAP_CHECK(condition, bootstrap_err) \ + LAZY_STREAM(BOOTSTRAP_LOG_STREAM(FATAL, bootstrap_err), !(condition)) \ + << "Check failed: " # condition << ". " + +#define BOOTSTRAP_DLOG(severity, bootstrap_err) \ + LAZY_STREAM(BOOTSTRAP_LOG_STREAM(severity, bootstrap_err), \ + DLOG_IS_ON(severity)) +#define BOOTSTRAP_DLOG_IF(severity, condition, bootstrap_err) \ + LAZY_STREAM(BOOTSTRAP_LOG_STREAM(severity, bootstrap_err), \ + DLOG_IS_ON(severity) && (condition)) + +#define BOOTSTRAP_DVLOG(verbose_level, bootstrap_err) \ + LAZY_STREAM(BOOTSTRAP_VLOG_STREAM(verbose_level, bootstrap_err), \ + BOOTSTRAP_DVLOG_IS_ON(verbose_level)) +#define BOOTSTRAP_DVLOG_IF(verbose_level, condition, bootstrap_err) \ + LAZY_STREAM(BOOTSTRAP_VLOG_STREAM(verbose_level, bootstrap_err), \ + BOOTSTRAP_DVLOG_IS_ON(verbose_level) && (condition)) + +#define BOOTSTRAP_DCHECK(condition, bootstrap_err) \ + LAZY_STREAM(BOOTSTRAP_LOG_STREAM(FATAL, bootstrap_err), \ + DCHECK_IS_ON() && !(condition)) \ + << "Check failed: " #condition << ". " + +#endif // BUILDFLAG(USE_BLINK) + +#endif // BASE_APPLE_MACH_LOGGING_H_
diff --git a/base/mac/scoped_dispatch_object.h b/base/apple/scoped_dispatch_object.h similarity index 83% rename from base/mac/scoped_dispatch_object.h rename to base/apple/scoped_dispatch_object.h index 1dd2c68..fc0f37a 100644 --- a/base/mac/scoped_dispatch_object.h +++ b/base/apple/scoped_dispatch_object.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef BASE_MAC_SCOPED_DISPATCH_OBJECT_H_ -#define BASE_MAC_SCOPED_DISPATCH_OBJECT_H_ +#ifndef BASE_APPLE_SCOPED_DISPATCH_OBJECT_H_ +#define BASE_APPLE_SCOPED_DISPATCH_OBJECT_H_ #include <dispatch/dispatch.h> @@ -28,9 +28,7 @@ dispatch_retain(object); return object; } - static void Release(T object) { - dispatch_release(object); - } + static void Release(T object) { dispatch_release(object); } }; } // namespace internal @@ -41,4 +39,4 @@ } // namespace base -#endif // BASE_MAC_SCOPED_DISPATCH_OBJECT_H_ +#endif // BASE_APPLE_SCOPED_DISPATCH_OBJECT_H_
diff --git a/base/mac/scoped_mach_port.cc b/base/apple/scoped_mach_port.cc similarity index 92% rename from base/mac/scoped_mach_port.cc rename to base/apple/scoped_mach_port.cc index 6dea806..c5bb5ca3 100644 --- a/base/mac/scoped_mach_port.cc +++ b/base/apple/scoped_mach_port.cc
@@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/mac/scoped_mach_port.h" +#include "base/apple/scoped_mach_port.h" -#include "base/mac/mach_logging.h" +#include "base/apple/mach_logging.h" -namespace base::mac { +namespace base::apple { namespace internal { // static @@ -65,10 +65,11 @@ ScopedMachSendRight RetainMachSendRight(mach_port_t port) { kern_return_t kr = mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, 1); - if (kr == KERN_SUCCESS) + if (kr == KERN_SUCCESS) { return ScopedMachSendRight(port); + } MACH_DLOG(ERROR, kr) << "mach_port_mod_refs +1"; return {}; } -} // namespace base::mac +} // namespace base::apple
diff --git a/base/apple/scoped_mach_port.h b/base/apple/scoped_mach_port.h new file mode 100644 index 0000000..6b3236e --- /dev/null +++ b/base/apple/scoped_mach_port.h
@@ -0,0 +1,79 @@ +// Copyright 2012 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_APPLE_SCOPED_MACH_PORT_H_ +#define BASE_APPLE_SCOPED_MACH_PORT_H_ + +#include <mach/mach.h> + +#include "base/base_export.h" +#include "base/scoped_generic.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace base::apple { + +namespace internal { + +struct BASE_EXPORT SendRightTraits { + static mach_port_t InvalidValue() { + return MACH_PORT_NULL; + } + + BASE_EXPORT static void Free(mach_port_t port); +}; + +struct BASE_EXPORT ReceiveRightTraits { + static mach_port_t InvalidValue() { + return MACH_PORT_NULL; + } + + BASE_EXPORT static void Free(mach_port_t port); +}; + +struct PortSetTraits { + static mach_port_t InvalidValue() { + return MACH_PORT_NULL; + } + + BASE_EXPORT static void Free(mach_port_t port); +}; + +} // namespace internal + +// A scoper for handling a Mach port that names a send right. Send rights are +// reference counted, and this takes ownership of the right on construction +// and then removes a reference to the right on destruction. If the reference +// is the last one on the right, the right is deallocated. +using ScopedMachSendRight = + ScopedGeneric<mach_port_t, internal::SendRightTraits>; + +// A scoper for handling a Mach port's receive right. There is only one +// receive right per port. This takes ownership of the receive right on +// construction and then destroys the right on destruction, turning all +// outstanding send rights into dead names. +using ScopedMachReceiveRight = + ScopedGeneric<mach_port_t, internal::ReceiveRightTraits>; + +// A scoper for handling a Mach port set. A port set can have only one +// reference. This takes ownership of that single reference on construction and +// destroys the port set on destruction. Destroying a port set does not destroy +// the receive rights that are members of the port set. +using ScopedMachPortSet = ScopedGeneric<mach_port_t, internal::PortSetTraits>; + +// Constructs a Mach port receive right and places the result in |receive|. +// If |send| is non-null, a send right will be created as well and stored +// there. If |queue_limit| is specified, the receive right will be constructed +// with the specified mpo_qlmit. Returns true on success and false on failure. +BASE_EXPORT bool CreateMachPort( + ScopedMachReceiveRight* receive, + ScopedMachSendRight* send, + absl::optional<mach_port_msgcount_t> queue_limit = absl::nullopt); + +// Increases the user reference count for MACH_PORT_RIGHT_SEND by 1 and returns +// a new scoper to manage the additional right. +BASE_EXPORT ScopedMachSendRight RetainMachSendRight(mach_port_t port); + +} // namespace base::apple + +#endif // BASE_APPLE_SCOPED_MACH_PORT_H_
diff --git a/base/mac/scoped_mach_vm.cc b/base/apple/scoped_mach_vm.cc similarity index 88% rename from base/mac/scoped_mach_vm.cc rename to base/apple/scoped_mach_vm.cc index e05a5f1..1d2a3c6 100644 --- a/base/mac/scoped_mach_vm.cc +++ b/base/apple/scoped_mach_vm.cc
@@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/mac/scoped_mach_vm.h" +#include "base/apple/scoped_mach_vm.h" -#include "base/mac/mach_logging.h" +#include "base/apple/mach_logging.h" -namespace base::mac { +namespace base::apple { void ScopedMachVM::reset(vm_address_t address, vm_size_t size) { DCHECK_EQ(address % PAGE_SIZE, 0u); @@ -33,4 +33,4 @@ size_ = size; } -} // namespace base::mac +} // namespace base::apple
diff --git a/base/apple/scoped_mach_vm.h b/base/apple/scoped_mach_vm.h new file mode 100644 index 0000000..cb9765d --- /dev/null +++ b/base/apple/scoped_mach_vm.h
@@ -0,0 +1,100 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_APPLE_SCOPED_MACH_VM_H_ +#define BASE_APPLE_SCOPED_MACH_VM_H_ + +#include <mach/mach.h> +#include <stddef.h> + +#include <algorithm> +#include <utility> + +#include "base/base_export.h" +#include "base/check_op.h" + +// Use ScopedMachVM to supervise ownership of pages in the current process +// through the Mach VM subsystem. Pages allocated with vm_allocate can be +// released when exiting a scope with ScopedMachVM. +// +// The Mach VM subsystem operates on a page-by-page basis, and a single VM +// allocation managed by a ScopedMachVM object may span multiple pages. As far +// as Mach is concerned, allocated pages may be deallocated individually. This +// is in contrast to higher-level allocators such as malloc, where the base +// address of an allocation implies the size of an allocated block. +// Consequently, it is not sufficient to just pass the base address of an +// allocation to ScopedMachVM, it also needs to know the size of the +// allocation. To avoid any confusion, both the base address and size must +// be page-aligned. +// +// When dealing with Mach VM, base addresses will naturally be page-aligned, +// but user-specified sizes may not be. If there's a concern that a size is +// not page-aligned, use the mach_vm_round_page macro to correct it. +// +// Example: +// +// vm_address_t address = 0; +// vm_size_t size = 12345; // This requested size is not page-aligned. +// kern_return_t kr = +// vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE); +// if (kr != KERN_SUCCESS) { +// return false; +// } +// ScopedMachVM vm_owner(address, mach_vm_round_page(size)); + +namespace base::apple { + +class BASE_EXPORT ScopedMachVM { + public: + explicit ScopedMachVM(vm_address_t address = 0, vm_size_t size = 0) + : address_(address), size_(size) { + DCHECK_EQ(address % PAGE_SIZE, 0u); + DCHECK_EQ(size % PAGE_SIZE, 0u); + } + + ScopedMachVM(const ScopedMachVM&) = delete; + ScopedMachVM& operator=(const ScopedMachVM&) = delete; + + ~ScopedMachVM() { + if (size_) { + vm_deallocate(mach_task_self(), address_, size_); + } + } + + // Resets the scoper to manage a new memory region. Both |address| and |size| + // must be page-aligned. If the new region is a smaller subset of the + // existing region (i.e. the new and old regions overlap), the non- + // overlapping part of the old region is deallocated. + void reset(vm_address_t address = 0, vm_size_t size = 0); + + // Like reset() but does not DCHECK that |address| and |size| are page- + // aligned. + void reset_unaligned(vm_address_t address, vm_size_t size); + + vm_address_t address() const { + return address_; + } + + vm_size_t size() const { + return size_; + } + + void swap(ScopedMachVM& that) { + std::swap(address_, that.address_); + std::swap(size_, that.size_); + } + + void release() { + address_ = 0; + size_ = 0; + } + + private: + vm_address_t address_; + vm_size_t size_; +}; + +} // namespace base::apple + +#endif // BASE_APPLE_SCOPED_MACH_VM_H_
diff --git a/base/mac/scoped_mach_vm_unittest.cc b/base/apple/scoped_mach_vm_unittest.cc similarity index 98% rename from base/mac/scoped_mach_vm_unittest.cc rename to base/apple/scoped_mach_vm_unittest.cc index 7611c98..32170d4 100644 --- a/base/mac/scoped_mach_vm_unittest.cc +++ b/base/apple/scoped_mach_vm_unittest.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 "base/mac/scoped_mach_vm.h" +#include "base/apple/scoped_mach_vm.h" #include <mach/mach.h> @@ -17,7 +17,7 @@ // allocation will report being part of the previously-deallocated large region. // That will cause the GetRegionInfo() expectations to fail. -namespace base::mac { +namespace base::apple { namespace { void GetRegionInfo(vm_address_t* region_address, vm_size_t* region_size) { @@ -228,4 +228,4 @@ #endif // DCHECK_IS_ON() } // namespace -} // namespace base::mac +} // namespace base::apple
diff --git a/base/mac/scoped_nsautorelease_pool.cc b/base/apple/scoped_nsautorelease_pool.cc similarity index 90% rename from base/mac/scoped_nsautorelease_pool.cc rename to base/apple/scoped_nsautorelease_pool.cc index b9b28b79..5793482 100644 --- a/base/mac/scoped_nsautorelease_pool.cc +++ b/base/apple/scoped_nsautorelease_pool.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 "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" // Note that this uses the direct runtime interface to the autorelease pool. // https://clang.llvm.org/docs/AutomaticReferenceCounting.html#runtime-support @@ -13,7 +13,7 @@ void objc_autoreleasePoolPop(void* pool); } -namespace base::mac { +namespace base::apple { ScopedNSAutoreleasePool::ScopedNSAutoreleasePool() : autorelease_pool_(objc_autoreleasePoolPush()) {} @@ -31,4 +31,4 @@ autorelease_pool_ = objc_autoreleasePoolPush(); } -} // namespace base::mac +} // namespace base::apple
diff --git a/base/apple/scoped_nsautorelease_pool.h b/base/apple/scoped_nsautorelease_pool.h new file mode 100644 index 0000000..84ac268 --- /dev/null +++ b/base/apple/scoped_nsautorelease_pool.h
@@ -0,0 +1,55 @@ +// Copyright 2011 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_APPLE_SCOPED_NSAUTORELEASE_POOL_H_ +#define BASE_APPLE_SCOPED_NSAUTORELEASE_POOL_H_ + +#include "base/base_export.h" +#include "base/memory/raw_ptr_exclusion.h" +#include "base/threading/thread_checker.h" + +namespace base::apple { + +// ScopedNSAutoreleasePool creates an autorelease pool when instantiated and +// pops it when destroyed. This allows an autorelease pool to be maintained in +// ordinary C++ code without bringing in any direct Objective-C dependency. +// +// Before using, please be aware that the semantics of autorelease pools do not +// match the semantics of a C++ class. In particular, recycling or destructing a +// pool lower on the stack destroys all pools higher on the stack, which does +// not mesh well with the existence of C++ objects for each pool. +// +// TODO(https://crbug.com/1424190): Enforce stack-only use via the +// STACK_ALLOCATED annotation. +// +// Use this class only in C++ code; use @autoreleasepool in Obj-C(++) code. + +class BASE_EXPORT ScopedNSAutoreleasePool { + public: + ScopedNSAutoreleasePool(); + + ScopedNSAutoreleasePool(const ScopedNSAutoreleasePool&) = delete; + ScopedNSAutoreleasePool& operator=(const ScopedNSAutoreleasePool&) = delete; + ScopedNSAutoreleasePool(ScopedNSAutoreleasePool&&) = delete; + ScopedNSAutoreleasePool& operator=(ScopedNSAutoreleasePool&&) = delete; + + ~ScopedNSAutoreleasePool(); + + // Clear out the pool in case its position on the stack causes it to be alive + // for long periods of time (such as the entire length of the app). Only use + // then when you're certain the items currently in the pool are no longer + // needed. + void Recycle(); + + private: + // This field is not a raw_ptr<> because it is a pointer to an Objective-C + // object. + RAW_PTR_EXCLUSION void* autorelease_pool_ GUARDED_BY_CONTEXT(thread_checker_); + + THREAD_CHECKER(thread_checker_); +}; + +} // namespace base::apple + +#endif // BASE_APPLE_SCOPED_NSAUTORELEASE_POOL_H_
diff --git a/base/files/file_path_watcher_fsevents.h b/base/files/file_path_watcher_fsevents.h index 989aed20..5310df5 100644 --- a/base/files/file_path_watcher_fsevents.h +++ b/base/files/file_path_watcher_fsevents.h
@@ -10,9 +10,9 @@ #include <vector> +#include "base/apple/scoped_dispatch_object.h" #include "base/files/file_path.h" #include "base/files/file_path_watcher.h" -#include "base/mac/scoped_dispatch_object.h" #include "base/memory/weak_ptr.h" namespace base {
diff --git a/base/mac/mach_logging.h b/base/mac/mach_logging.h index e29be96..529bd96 100644 --- a/base/mac/mach_logging.h +++ b/base/mac/mach_logging.h
@@ -1,171 +1,16 @@ -// Copyright 2014 The Chromium Authors +// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_MAC_MACH_LOGGING_H_ #define BASE_MAC_MACH_LOGGING_H_ -#include <mach/mach.h> +#include "base/apple/mach_logging.h" -#include "base/base_export.h" -#include "base/logging.h" -#include "build/blink_buildflags.h" -#include "build/build_config.h" +// This is a forwarding header so that Crashpad can continue to build correctly +// until mini_chromium and then it are updated and rolled. -// Use the MACH_LOG family of macros along with a mach_error_t (kern_return_t) -// containing a Mach error. The error value will be decoded so that logged -// messages explain the error. -// -// Use the BOOTSTRAP_LOG family of macros specifically for errors that occur -// while interoperating with the bootstrap subsystem. These errors will first -// be looked up as bootstrap error messages. If no match is found, they will -// be treated as generic Mach errors, as in MACH_LOG. -// -// Examples: -// -// kern_return_t kr = mach_timebase_info(&info); -// if (kr != KERN_SUCCESS) { -// MACH_LOG(ERROR, kr) << "mach_timebase_info"; -// } -// -// kr = vm_deallocate(task, address, size); -// MACH_DCHECK(kr == KERN_SUCCESS, kr) << "vm_deallocate"; - -namespace logging { - -class BASE_EXPORT MachLogMessage : public logging::LogMessage { - public: - MachLogMessage(const char* file_path, - int line, - LogSeverity severity, - mach_error_t mach_err); - - MachLogMessage(const MachLogMessage&) = delete; - MachLogMessage& operator=(const MachLogMessage&) = delete; - - ~MachLogMessage() override; - - private: - mach_error_t mach_err_; -}; - -} // namespace logging - -#if DCHECK_IS_ON() -#define MACH_DVLOG_IS_ON(verbose_level) VLOG_IS_ON(verbose_level) -#else -#define MACH_DVLOG_IS_ON(verbose_level) 0 -#endif - -#define MACH_LOG_STREAM(severity, mach_err) \ - COMPACT_GOOGLE_LOG_EX_ ## severity(MachLogMessage, mach_err).stream() -#define MACH_VLOG_STREAM(verbose_level, mach_err) \ - logging::MachLogMessage(__FILE__, __LINE__, \ - -verbose_level, mach_err).stream() - -#define MACH_LOG(severity, mach_err) \ - LAZY_STREAM(MACH_LOG_STREAM(severity, mach_err), LOG_IS_ON(severity)) -#define MACH_LOG_IF(severity, condition, mach_err) \ - LAZY_STREAM(MACH_LOG_STREAM(severity, mach_err), \ - LOG_IS_ON(severity) && (condition)) - -#define MACH_VLOG(verbose_level, mach_err) \ - LAZY_STREAM(MACH_VLOG_STREAM(verbose_level, mach_err), \ - VLOG_IS_ON(verbose_level)) -#define MACH_VLOG_IF(verbose_level, condition, mach_err) \ - LAZY_STREAM(MACH_VLOG_STREAM(verbose_level, mach_err), \ - VLOG_IS_ON(verbose_level) && (condition)) - -#define MACH_CHECK(condition, mach_err) \ - LAZY_STREAM(MACH_LOG_STREAM(FATAL, mach_err), !(condition)) \ - << "Check failed: " # condition << ". " - -#define MACH_DLOG(severity, mach_err) \ - LAZY_STREAM(MACH_LOG_STREAM(severity, mach_err), DLOG_IS_ON(severity)) -#define MACH_DLOG_IF(severity, condition, mach_err) \ - LAZY_STREAM(MACH_LOG_STREAM(severity, mach_err), \ - DLOG_IS_ON(severity) && (condition)) - -#define MACH_DVLOG(verbose_level, mach_err) \ - LAZY_STREAM(MACH_VLOG_STREAM(verbose_level, mach_err), \ - MACH_DVLOG_IS_ON(verbose_level)) -#define MACH_DVLOG_IF(verbose_level, condition, mach_err) \ - LAZY_STREAM(MACH_VLOG_STREAM(verbose_level, mach_err), \ - MACH_DVLOG_IS_ON(verbose_level) && (condition)) - -#define MACH_DCHECK(condition, mach_err) \ - LAZY_STREAM(MACH_LOG_STREAM(FATAL, mach_err), \ - DCHECK_IS_ON() && !(condition)) \ - << "Check failed: " #condition << ". " - -#if BUILDFLAG(USE_BLINK) - -namespace logging { - -class BASE_EXPORT BootstrapLogMessage : public logging::LogMessage { - public: - BootstrapLogMessage(const char* file_path, - int line, - LogSeverity severity, - kern_return_t bootstrap_err); - - BootstrapLogMessage(const BootstrapLogMessage&) = delete; - BootstrapLogMessage& operator=(const BootstrapLogMessage&) = delete; - - ~BootstrapLogMessage() override; - - private: - kern_return_t bootstrap_err_; -}; - -} // namespace logging - -#define BOOTSTRAP_DVLOG_IS_ON MACH_DVLOG_IS_ON - -#define BOOTSTRAP_LOG_STREAM(severity, bootstrap_err) \ - COMPACT_GOOGLE_LOG_EX_ ## severity(BootstrapLogMessage, \ - bootstrap_err).stream() -#define BOOTSTRAP_VLOG_STREAM(verbose_level, bootstrap_err) \ - logging::BootstrapLogMessage(__FILE__, __LINE__, \ - -verbose_level, bootstrap_err).stream() - -#define BOOTSTRAP_LOG(severity, bootstrap_err) \ - LAZY_STREAM(BOOTSTRAP_LOG_STREAM(severity, \ - bootstrap_err), LOG_IS_ON(severity)) -#define BOOTSTRAP_LOG_IF(severity, condition, bootstrap_err) \ - LAZY_STREAM(BOOTSTRAP_LOG_STREAM(severity, bootstrap_err), \ - LOG_IS_ON(severity) && (condition)) - -#define BOOTSTRAP_VLOG(verbose_level, bootstrap_err) \ - LAZY_STREAM(BOOTSTRAP_VLOG_STREAM(verbose_level, bootstrap_err), \ - VLOG_IS_ON(verbose_level)) -#define BOOTSTRAP_VLOG_IF(verbose_level, condition, bootstrap_err) \ - LAZY_STREAM(BOOTSTRAP_VLOG_STREAM(verbose_level, bootstrap_err), \ - VLOG_IS_ON(verbose_level) && (condition)) - -#define BOOTSTRAP_CHECK(condition, bootstrap_err) \ - LAZY_STREAM(BOOTSTRAP_LOG_STREAM(FATAL, bootstrap_err), !(condition)) \ - << "Check failed: " # condition << ". " - -#define BOOTSTRAP_DLOG(severity, bootstrap_err) \ - LAZY_STREAM(BOOTSTRAP_LOG_STREAM(severity, bootstrap_err), \ - DLOG_IS_ON(severity)) -#define BOOTSTRAP_DLOG_IF(severity, condition, bootstrap_err) \ - LAZY_STREAM(BOOTSTRAP_LOG_STREAM(severity, bootstrap_err), \ - DLOG_IS_ON(severity) && (condition)) - -#define BOOTSTRAP_DVLOG(verbose_level, bootstrap_err) \ - LAZY_STREAM(BOOTSTRAP_VLOG_STREAM(verbose_level, bootstrap_err), \ - BOOTSTRAP_DVLOG_IS_ON(verbose_level)) -#define BOOTSTRAP_DVLOG_IF(verbose_level, condition, bootstrap_err) \ - LAZY_STREAM(BOOTSTRAP_VLOG_STREAM(verbose_level, bootstrap_err), \ - BOOTSTRAP_DVLOG_IS_ON(verbose_level) && (condition)) - -#define BOOTSTRAP_DCHECK(condition, bootstrap_err) \ - LAZY_STREAM(BOOTSTRAP_LOG_STREAM(FATAL, bootstrap_err), \ - DCHECK_IS_ON() && !(condition)) \ - << "Check failed: " #condition << ". " - -#endif // BUILDFLAG(USE_BLINK) +// TODO(https://crbug.com/1444927): Update mini_chromium, update Crashpad, roll +// Crashpad, and then delete this forwarding header. #endif // BASE_MAC_MACH_LOGGING_H_
diff --git a/base/mac/mach_port_rendezvous.cc b/base/mac/mach_port_rendezvous.cc index 6efa77f..71806bde 100644 --- a/base/mac/mach_port_rendezvous.cc +++ b/base/mac/mach_port_rendezvous.cc
@@ -9,10 +9,10 @@ #include <utility> +#include "base/apple/mach_logging.h" #include "base/containers/buffer_iterator.h" #include "base/logging.h" #include "base/mac/foundation_util.h" -#include "base/mac/mach_logging.h" #include "base/mac/scoped_mach_msg_destroy.h" #include "base/notreached.h" #include "base/strings/stringprintf.h" @@ -59,11 +59,11 @@ disposition == MACH_MSG_TYPE_MAKE_SEND_ONCE); } -MachRendezvousPort::MachRendezvousPort(mac::ScopedMachSendRight send_right) +MachRendezvousPort::MachRendezvousPort(apple::ScopedMachSendRight send_right) : name_(send_right.release()), disposition_(MACH_MSG_TYPE_MOVE_SEND) {} MachRendezvousPort::MachRendezvousPort( - mac::ScopedMachReceiveRight receive_right) + apple::ScopedMachReceiveRight receive_right) : name_(receive_right.release()), disposition_(MACH_MSG_TYPE_MOVE_RECEIVE) {} @@ -143,7 +143,7 @@ StringPrintf(kBootstrapNameFormat, mac::BaseBundleID(), getpid()); kern_return_t kr = bootstrap_check_in( bootstrap_port, bootstrap_name.c_str(), - mac::ScopedMachReceiveRight::Receiver(server_port_).get()); + apple::ScopedMachReceiveRight::Receiver(server_port_).get()); BOOTSTRAP_CHECK(kr == KERN_SUCCESS, kr) << "bootstrap_check_in " << bootstrap_name; @@ -277,21 +277,21 @@ return client; } -mac::ScopedMachSendRight MachPortRendezvousClient::TakeSendRight( +apple::ScopedMachSendRight MachPortRendezvousClient::TakeSendRight( MachPortsForRendezvous::key_type key) { MachRendezvousPort port = PortForKey(key); DCHECK(port.disposition() == 0 || port.disposition() == MACH_MSG_TYPE_PORT_SEND || port.disposition() == MACH_MSG_TYPE_PORT_SEND_ONCE); - return mac::ScopedMachSendRight(port.name()); + return apple::ScopedMachSendRight(port.name()); } -mac::ScopedMachReceiveRight MachPortRendezvousClient::TakeReceiveRight( +apple::ScopedMachReceiveRight MachPortRendezvousClient::TakeReceiveRight( MachPortsForRendezvous::key_type key) { MachRendezvousPort port = PortForKey(key); DCHECK(port.disposition() == 0 || port.disposition() == MACH_MSG_TYPE_PORT_RECEIVE); - return mac::ScopedMachReceiveRight(port.name()); + return apple::ScopedMachReceiveRight(port.name()); } size_t MachPortRendezvousClient::GetPortCount() { @@ -307,11 +307,11 @@ bool MachPortRendezvousClient::AcquirePorts() { AutoLock lock(lock_); - mac::ScopedMachSendRight server_port; + apple::ScopedMachSendRight server_port; std::string bootstrap_name = GetBootstrapName(); kern_return_t kr = bootstrap_look_up( bootstrap_port, const_cast<char*>(bootstrap_name.c_str()), - mac::ScopedMachSendRight::Receiver(server_port).get()); + apple::ScopedMachSendRight::Receiver(server_port).get()); if (kr != KERN_SUCCESS) { BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up " << bootstrap_name; return false; @@ -321,7 +321,7 @@ } bool MachPortRendezvousClient::SendRequest( - mac::ScopedMachSendRight server_port) { + apple::ScopedMachSendRight server_port) { const size_t buffer_size = CalculateResponseSize(kMaximumRendezvousPorts) + sizeof(mach_msg_trailer_t); auto buffer = std::make_unique<uint8_t[]>(buffer_size);
diff --git a/base/mac/mach_port_rendezvous.h b/base/mac/mach_port_rendezvous.h index e704594..364f832f 100644 --- a/base/mac/mach_port_rendezvous.h +++ b/base/mac/mach_port_rendezvous.h
@@ -15,9 +15,9 @@ #include <string> #include "base/apple/dispatch_source_mach.h" +#include "base/apple/scoped_dispatch_object.h" +#include "base/apple/scoped_mach_port.h" #include "base/base_export.h" -#include "base/mac/scoped_dispatch_object.h" -#include "base/mac/scoped_mach_port.h" #include "base/synchronization/lock.h" #include "base/thread_annotations.h" @@ -48,9 +48,9 @@ // Creates a rendezvous port that allows specifying the specific disposition. MachRendezvousPort(mach_port_t name, mach_msg_type_name_t disposition); // Creates a rendezvous port for MACH_MSG_TYPE_MOVE_SEND. - explicit MachRendezvousPort(mac::ScopedMachSendRight send_right); + explicit MachRendezvousPort(apple::ScopedMachSendRight send_right); // Creates a rendezvous port for MACH_MSG_TYPE_MOVE_RECEIVE. - explicit MachRendezvousPort(mac::ScopedMachReceiveRight receive_right); + explicit MachRendezvousPort(apple::ScopedMachReceiveRight receive_right); // Note that the destructor does not call Destroy() explicitly. // To avoid leaking ports, either use dispositions that create rights during @@ -147,7 +147,7 @@ // The Mach receive right for the server. A send right to this is port is // registered in the bootstrap server. - mac::ScopedMachReceiveRight server_port_; + apple::ScopedMachReceiveRight server_port_; // Mach message dispatch source for |server_port_|. std::unique_ptr<DispatchSourceMach> dispatch_source_; @@ -175,13 +175,14 @@ // right exists, or it was already taken, returns an invalid right. Safe to // call from any thread. DCHECKs if the right referenced by |key| is not a // send or send-once right. - mac::ScopedMachSendRight TakeSendRight(MachPortsForRendezvous::key_type key); + apple::ScopedMachSendRight TakeSendRight( + MachPortsForRendezvous::key_type key); // Returns the Mach receive right that was registered with |key|. If no such // right exists, or it was already taken, returns an invalid right. Safe to // call from any thread. DCHECKs if the right referenced by |key| is not a // receive right. - mac::ScopedMachReceiveRight TakeReceiveRight( + apple::ScopedMachReceiveRight TakeReceiveRight( MachPortsForRendezvous::key_type key); // Returns the number of ports in the client. After PerformRendezvous(), this @@ -201,7 +202,7 @@ bool AcquirePorts(); // Sends the actual IPC message to |server_port| and parses the reply. - bool SendRequest(mac::ScopedMachSendRight server_port) + bool SendRequest(apple::ScopedMachSendRight server_port) EXCLUSIVE_LOCKS_REQUIRED(lock_); // Returns a MachRendezvousPort for a given key and removes it from the
diff --git a/base/mac/mach_port_rendezvous_fuzzer.cc b/base/mac/mach_port_rendezvous_fuzzer.cc index c56c1cc..e49cecc 100644 --- a/base/mac/mach_port_rendezvous_fuzzer.cc +++ b/base/mac/mach_port_rendezvous_fuzzer.cc
@@ -4,8 +4,8 @@ #include "base/mac/mach_port_rendezvous.h" +#include "base/apple/mach_logging.h" #include "base/logging.h" -#include "base/mac/mach_logging.h" #include "base/synchronization/lock.h" #include "testing/libfuzzer/fuzzers/mach/mach_message_converter.h" #include "testing/libfuzzer/proto/lpm_interface.h" @@ -30,7 +30,7 @@ base::MachPortRendezvousServer::GetInstance()->client_data_.clear(); } - base::mac::ScopedMachSendRight server_send_right; + base::apple::ScopedMachSendRight server_send_right; }; } // namespace base
diff --git a/base/mac/mach_port_rendezvous_unittest.cc b/base/mac/mach_port_rendezvous_unittest.cc index 89c7fbc..7f23e4c3 100644 --- a/base/mac/mach_port_rendezvous_unittest.cc +++ b/base/mac/mach_port_rendezvous_unittest.cc
@@ -8,9 +8,9 @@ #include <utility> +#include "base/apple/mach_logging.h" #include "base/at_exit.h" #include "base/mac/foundation_util.h" -#include "base/mac/mach_logging.h" #include "base/strings/stringprintf.h" #include "base/test/multiprocess_test.h" #include "base/test/test_timeouts.h" @@ -44,7 +44,7 @@ CHECK_EQ(1u, rendezvous_client->GetPortCount()); - mac::ScopedMachSendRight port = + apple::ScopedMachSendRight port = rendezvous_client->TakeSendRight(kTestPortKey); CHECK(port.is_valid()); @@ -66,10 +66,10 @@ auto* server = MachPortRendezvousServer::GetInstance(); ASSERT_TRUE(server); - mac::ScopedMachReceiveRight port; + apple::ScopedMachReceiveRight port; kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, - mac::ScopedMachReceiveRight::Receiver(port).get()); + apple::ScopedMachReceiveRight::Receiver(port).get()); ASSERT_EQ(kr, KERN_SUCCESS); MachRendezvousPort rendezvous_port(port.get(), MACH_MSG_TYPE_MAKE_SEND); @@ -127,10 +127,10 @@ auto* server = MachPortRendezvousServer::GetInstance(); ASSERT_TRUE(server); - mac::ScopedMachReceiveRight port; + apple::ScopedMachReceiveRight port; kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, - mac::ScopedMachReceiveRight::Receiver(port).get()); + apple::ScopedMachReceiveRight::Receiver(port).get()); ASSERT_EQ(kr, KERN_SUCCESS); MachRendezvousPort rendezvous_port(port.get(), MACH_MSG_TYPE_MAKE_SEND);
diff --git a/base/mac/scoped_mach_port.h b/base/mac/scoped_mach_port.h index f56929b..e81d96fb 100644 --- a/base/mac/scoped_mach_port.h +++ b/base/mac/scoped_mach_port.h
@@ -1,78 +1,23 @@ -// Copyright 2012 The Chromium Authors +// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_MAC_SCOPED_MACH_PORT_H_ #define BASE_MAC_SCOPED_MACH_PORT_H_ -#include <mach/mach.h> +#include "base/apple/scoped_mach_port.h" -#include "base/base_export.h" -#include "base/scoped_generic.h" -#include "third_party/abseil-cpp/absl/types/optional.h" +// This is a forwarding header so that Crashpad can continue to build correctly +// until mini_chromium and then it are updated and rolled. + +// TODO(https://crbug.com/1444927): Update mini_chromium, update Crashpad, roll +// Crashpad, and then delete this forwarding header. namespace base::mac { -namespace internal { - -struct BASE_EXPORT SendRightTraits { - static mach_port_t InvalidValue() { - return MACH_PORT_NULL; - } - - BASE_EXPORT static void Free(mach_port_t port); -}; - -struct BASE_EXPORT ReceiveRightTraits { - static mach_port_t InvalidValue() { - return MACH_PORT_NULL; - } - - BASE_EXPORT static void Free(mach_port_t port); -}; - -struct PortSetTraits { - static mach_port_t InvalidValue() { - return MACH_PORT_NULL; - } - - BASE_EXPORT static void Free(mach_port_t port); -}; - -} // namespace internal - -// A scoper for handling a Mach port that names a send right. Send rights are -// reference counted, and this takes ownership of the right on construction -// and then removes a reference to the right on destruction. If the reference -// is the last one on the right, the right is deallocated. -using ScopedMachSendRight = - ScopedGeneric<mach_port_t, internal::SendRightTraits>; - -// A scoper for handling a Mach port's receive right. There is only one -// receive right per port. This takes ownership of the receive right on -// construction and then destroys the right on destruction, turning all -// outstanding send rights into dead names. -using ScopedMachReceiveRight = - ScopedGeneric<mach_port_t, internal::ReceiveRightTraits>; - -// A scoper for handling a Mach port set. A port set can have only one -// reference. This takes ownership of that single reference on construction and -// destroys the port set on destruction. Destroying a port set does not destroy -// the receive rights that are members of the port set. -using ScopedMachPortSet = ScopedGeneric<mach_port_t, internal::PortSetTraits>; - -// Constructs a Mach port receive right and places the result in |receive|. -// If |send| is non-null, a send right will be created as well and stored -// there. If |queue_limit| is specified, the receive right will be constructed -// with the specified mpo_qlmit. Returns true on success and false on failure. -BASE_EXPORT bool CreateMachPort( - ScopedMachReceiveRight* receive, - ScopedMachSendRight* send, - absl::optional<mach_port_msgcount_t> queue_limit = absl::nullopt); - -// Increases the user reference count for MACH_PORT_RIGHT_SEND by 1 and returns -// a new scoper to manage the additional right. -BASE_EXPORT ScopedMachSendRight RetainMachSendRight(mach_port_t port); +using ScopedMachSendRight = base::apple::ScopedMachSendRight; +using ScopedMachReceiveRight = base::apple::ScopedMachReceiveRight; +using ScopedMachPortSet = base::apple::ScopedMachPortSet; } // namespace base::mac
diff --git a/base/mac/scoped_mach_vm.h b/base/mac/scoped_mach_vm.h index e3359d9..a19d0152 100644 --- a/base/mac/scoped_mach_vm.h +++ b/base/mac/scoped_mach_vm.h
@@ -1,99 +1,21 @@ -// Copyright 2014 The Chromium Authors +// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_MAC_SCOPED_MACH_VM_H_ #define BASE_MAC_SCOPED_MACH_VM_H_ -#include <mach/mach.h> -#include <stddef.h> +#include "base/apple/scoped_mach_vm.h" -#include <algorithm> -#include <utility> +// This is a forwarding header so that Crashpad can continue to build correctly +// until mini_chromium and then it are updated and rolled. -#include "base/base_export.h" -#include "base/check_op.h" - -// Use ScopedMachVM to supervise ownership of pages in the current process -// through the Mach VM subsystem. Pages allocated with vm_allocate can be -// released when exiting a scope with ScopedMachVM. -// -// The Mach VM subsystem operates on a page-by-page basis, and a single VM -// allocation managed by a ScopedMachVM object may span multiple pages. As far -// as Mach is concerned, allocated pages may be deallocated individually. This -// is in contrast to higher-level allocators such as malloc, where the base -// address of an allocation implies the size of an allocated block. -// Consequently, it is not sufficient to just pass the base address of an -// allocation to ScopedMachVM, it also needs to know the size of the -// allocation. To avoid any confusion, both the base address and size must -// be page-aligned. -// -// When dealing with Mach VM, base addresses will naturally be page-aligned, -// but user-specified sizes may not be. If there's a concern that a size is -// not page-aligned, use the mach_vm_round_page macro to correct it. -// -// Example: -// -// vm_address_t address = 0; -// vm_size_t size = 12345; // This requested size is not page-aligned. -// kern_return_t kr = -// vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE); -// if (kr != KERN_SUCCESS) { -// return false; -// } -// ScopedMachVM vm_owner(address, mach_vm_round_page(size)); +// TODO(https://crbug.com/1444927): Update mini_chromium, update Crashpad, roll +// Crashpad, and then delete this forwarding header. namespace base::mac { -class BASE_EXPORT ScopedMachVM { - public: - explicit ScopedMachVM(vm_address_t address = 0, vm_size_t size = 0) - : address_(address), size_(size) { - DCHECK_EQ(address % PAGE_SIZE, 0u); - DCHECK_EQ(size % PAGE_SIZE, 0u); - } - - ScopedMachVM(const ScopedMachVM&) = delete; - ScopedMachVM& operator=(const ScopedMachVM&) = delete; - - ~ScopedMachVM() { - if (size_) { - vm_deallocate(mach_task_self(), address_, size_); - } - } - - // Resets the scoper to manage a new memory region. Both |address| and |size| - // must be page-aligned. If the new region is a smaller subset of the - // existing region (i.e. the new and old regions overlap), the non- - // overlapping part of the old region is deallocated. - void reset(vm_address_t address = 0, vm_size_t size = 0); - - // Like reset() but does not DCHECK that |address| and |size| are page- - // aligned. - void reset_unaligned(vm_address_t address, vm_size_t size); - - vm_address_t address() const { - return address_; - } - - vm_size_t size() const { - return size_; - } - - void swap(ScopedMachVM& that) { - std::swap(address_, that.address_); - std::swap(size_, that.size_); - } - - void release() { - address_ = 0; - size_ = 0; - } - - private: - vm_address_t address_; - vm_size_t size_; -}; +using ScopedMachVM = base::apple::ScopedMachVM; } // namespace base::mac
diff --git a/base/mac/scoped_nsautorelease_pool.h b/base/mac/scoped_nsautorelease_pool.h index d556cd5f..1cacfde 100644 --- a/base/mac/scoped_nsautorelease_pool.h +++ b/base/mac/scoped_nsautorelease_pool.h
@@ -1,54 +1,21 @@ -// Copyright 2011 The Chromium Authors +// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_ #define BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_ -#include "base/base_export.h" -#include "base/memory/raw_ptr_exclusion.h" -#include "base/threading/thread_checker.h" +#include "base/apple/scoped_nsautorelease_pool.h" + +// This is a forwarding header so that Crashpad can continue to build correctly +// until mini_chromium and then it are updated and rolled. + +// TODO(https://crbug.com/1444927): Update mini_chromium, update Crashpad, roll +// Crashpad, and then delete this forwarding header. namespace base::mac { -// ScopedNSAutoreleasePool creates an autorelease pool when instantiated and -// pops it when destroyed. This allows an autorelease pool to be maintained in -// ordinary C++ code without bringing in any direct Objective-C dependency. -// -// Before using, please be aware that the semantics of autorelease pools do not -// match the semantics of a C++ class. In particular, recycling or destructing a -// pool lower on the stack destroys all pools higher on the stack, which does -// not mesh well with the existence of C++ objects for each pool. -// -// TODO(https://crbug.com/1424190): Enforce stack-only use via the -// STACK_ALLOCATED annotation. -// -// Use this class only in C++ code; use @autoreleasepool in Obj-C(++) code. - -class BASE_EXPORT ScopedNSAutoreleasePool { - public: - ScopedNSAutoreleasePool(); - - ScopedNSAutoreleasePool(const ScopedNSAutoreleasePool&) = delete; - ScopedNSAutoreleasePool& operator=(const ScopedNSAutoreleasePool&) = delete; - ScopedNSAutoreleasePool(ScopedNSAutoreleasePool&&) = delete; - ScopedNSAutoreleasePool& operator=(ScopedNSAutoreleasePool&&) = delete; - - ~ScopedNSAutoreleasePool(); - - // Clear out the pool in case its position on the stack causes it to be alive - // for long periods of time (such as the entire length of the app). Only use - // then when you're certain the items currently in the pool are no longer - // needed. - void Recycle(); - - private: - // This field is not a raw_ptr<> because it is a pointer to an Objective-C - // object. - RAW_PTR_EXCLUSION void* autorelease_pool_ GUARDED_BY_CONTEXT(thread_checker_); - - THREAD_CHECKER(thread_checker_); -}; +using ScopedNSAutoreleasePool = base::apple::ScopedNSAutoreleasePool; } // namespace base::mac
diff --git a/base/memory/platform_shared_memory_handle.h b/base/memory/platform_shared_memory_handle.h index 9487788..42dc4ef 100644 --- a/base/memory/platform_shared_memory_handle.h +++ b/base/memory/platform_shared_memory_handle.h
@@ -9,7 +9,7 @@ #if BUILDFLAG(IS_APPLE) #include <mach/mach.h> -#include "base/mac/scoped_mach_port.h" +#include "base/apple/scoped_mach_port.h" #elif BUILDFLAG(IS_FUCHSIA) #include <lib/zx/vmo.h> #elif BUILDFLAG(IS_WIN) @@ -51,7 +51,7 @@ // Platform-specific shared memory type used by the shared memory system. #if BUILDFLAG(IS_APPLE) using PlatformSharedMemoryHandle = mach_port_t; -using ScopedPlatformSharedMemoryHandle = mac::ScopedMachSendRight; +using ScopedPlatformSharedMemoryHandle = apple::ScopedMachSendRight; #elif BUILDFLAG(IS_FUCHSIA) using PlatformSharedMemoryHandle = zx::unowned_vmo; using ScopedPlatformSharedMemoryHandle = zx::vmo;
diff --git a/base/memory/platform_shared_memory_mapper_apple.cc b/base/memory/platform_shared_memory_mapper_apple.cc index b3dd55c..1f44c1e 100644 --- a/base/memory/platform_shared_memory_mapper_apple.cc +++ b/base/memory/platform_shared_memory_mapper_apple.cc
@@ -7,7 +7,7 @@ #include "base/logging.h" #include <mach/vm_map.h> -#include "base/mac/mach_logging.h" +#include "base/apple/mach_logging.h" namespace base {
diff --git a/base/memory/platform_shared_memory_region_apple.cc b/base/memory/platform_shared_memory_region_apple.cc index 21f7089..1699d1ecc 100644 --- a/base/memory/platform_shared_memory_region_apple.cc +++ b/base/memory/platform_shared_memory_region_apple.cc
@@ -6,14 +6,14 @@ #include <mach/vm_map.h> -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_mach_vm.h" +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_vm.h" namespace base::subtle { // static PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take( - mac::ScopedMachSendRight handle, + apple::ScopedMachSendRight handle, Mode mode, size_t size, const UnguessableToken& guid) { @@ -59,7 +59,7 @@ return {}; } - return PlatformSharedMemoryRegion(mac::ScopedMachSendRight(handle_.get()), + return PlatformSharedMemoryRegion(apple::ScopedMachSendRight(handle_.get()), mode_, size_, guid_); } @@ -75,10 +75,10 @@ CHECK_EQ(mode_, Mode::kWritable) << "Only writable shared memory region can be converted to read-only"; - mac::ScopedMachSendRight handle_copy(handle_.release()); + apple::ScopedMachSendRight handle_copy(handle_.release()); void* temp_addr = mapped_addr; - mac::ScopedMachVM scoped_memory; + apple::ScopedMachVM scoped_memory; if (!temp_addr) { // Intentionally lower current prot and max prot to |VM_PROT_READ|. kern_return_t kr = @@ -95,11 +95,11 @@ // Make new memory object. memory_object_size_t allocation_size = size_; - mac::ScopedMachSendRight named_right; + apple::ScopedMachSendRight named_right; kern_return_t kr = mach_make_memory_entry_64( mach_task_self(), &allocation_size, reinterpret_cast<memory_object_offset_t>(temp_addr), VM_PROT_READ, - mac::ScopedMachSendRight::Receiver(named_right).get(), MACH_PORT_NULL); + apple::ScopedMachSendRight::Receiver(named_right).get(), MACH_PORT_NULL); if (kr != KERN_SUCCESS) { MACH_DLOG(ERROR, kr) << "mach_make_memory_entry_64"; return false; @@ -138,12 +138,12 @@ "lead to this region being non-modifiable"; memory_object_size_t vm_size = size; - mac::ScopedMachSendRight named_right; + apple::ScopedMachSendRight named_right; kern_return_t kr = mach_make_memory_entry_64( mach_task_self(), &vm_size, 0, // Address. MAP_MEM_NAMED_CREATE | VM_PROT_READ | VM_PROT_WRITE, - mac::ScopedMachSendRight::Receiver(named_right).get(), + apple::ScopedMachSendRight::Receiver(named_right).get(), MACH_PORT_NULL); // Parent handle. // Crash as soon as shm allocation fails to debug the issue // https://crbug.com/872237. @@ -190,7 +190,7 @@ } PlatformSharedMemoryRegion::PlatformSharedMemoryRegion( - mac::ScopedMachSendRight handle, + apple::ScopedMachSendRight handle, Mode mode, size_t size, const UnguessableToken& guid)
diff --git a/base/message_loop/message_pump_apple.mm b/base/message_loop/message_pump_apple.mm index 75d31eda..1457748e2 100644 --- a/base/message_loop/message_pump_apple.mm +++ b/base/message_loop/message_pump_apple.mm
@@ -11,11 +11,11 @@ #include <memory> #include "base/apple/call_with_eh_frame.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/auto_reset.h" #include "base/check_op.h" #include "base/feature_list.h" #include "base/mac/scoped_cftyperef.h" -#include "base/mac/scoped_nsautorelease_pool.h" #include "base/memory/raw_ptr.h" #include "base/memory/scoped_policy.h" #include "base/metrics/histogram_samples.h" @@ -86,7 +86,7 @@ OptionalAutoreleasePool& operator=(const OptionalAutoreleasePool&) = delete; private: - absl::optional<base::mac::ScopedNSAutoreleasePool> pool_; + absl::optional<base::apple::ScopedNSAutoreleasePool> pool_; }; class MessagePumpCFRunLoopBase::ScopedModeEnabler {
diff --git a/base/message_loop/message_pump_default.cc b/base/message_loop/message_pump_default.cc index bd1b5fd..c3efad1 100644 --- a/base/message_loop/message_pump_default.cc +++ b/base/message_loop/message_pump_default.cc
@@ -12,9 +12,9 @@ #if BUILDFLAG(IS_APPLE) #include <mach/thread_policy.h> -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_mach_port.h" -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/threading/threading_features.h" #endif @@ -34,7 +34,7 @@ for (;;) { #if BUILDFLAG(IS_APPLE) - mac::ScopedNSAutoreleasePool autorelease_pool; + apple::ScopedNSAutoreleasePool autorelease_pool; #endif Delegate::NextWorkInfo next_work_info = delegate->DoWork();
diff --git a/base/message_loop/message_pump_kqueue.cc b/base/message_loop/message_pump_kqueue.cc index 47aaf21..462a96b 100644 --- a/base/message_loop/message_pump_kqueue.cc +++ b/base/message_loop/message_pump_kqueue.cc
@@ -8,12 +8,12 @@ #include <atomic> +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/auto_reset.h" #include "base/feature_list.h" #include "base/logging.h" #include "base/mac/mac_util.h" -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_nsautorelease_pool.h" #include "base/notreached.h" #include "base/posix/eintr_wrapper.h" #include "base/time/time_override.h" @@ -130,7 +130,7 @@ // using an EVFILT_USER event, especially when triggered across threads. kern_return_t kr = mach_port_allocate( mach_task_self(), MACH_PORT_RIGHT_RECEIVE, - base::mac::ScopedMachReceiveRight::Receiver(wakeup_).get()); + base::apple::ScopedMachReceiveRight::Receiver(wakeup_).get()); MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_allocate"; // Configure the event to directly receive the Mach message as part of the @@ -162,7 +162,7 @@ RunSimplified(delegate); } else { while (keep_running_) { - mac::ScopedNSAutoreleasePool pool; + apple::ScopedNSAutoreleasePool pool; bool do_more_work = DoInternalWork(delegate, nullptr); if (!keep_running_) @@ -195,7 +195,7 @@ DoInternalWork(delegate, nullptr); while (keep_running_) { - mac::ScopedNSAutoreleasePool pool; + apple::ScopedNSAutoreleasePool pool; Delegate::NextWorkInfo next_work_info = delegate->DoWork(); if (!keep_running_)
diff --git a/base/message_loop/message_pump_kqueue.h b/base/message_loop/message_pump_kqueue.h index 177f02c..a2a163b 100644 --- a/base/message_loop/message_pump_kqueue.h +++ b/base/message_loop/message_pump_kqueue.h
@@ -11,10 +11,10 @@ #include <vector> +#include "base/apple/scoped_mach_port.h" #include "base/containers/id_map.h" #include "base/files/scoped_file.h" #include "base/location.h" -#include "base/mac/scoped_mach_port.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_pump.h" @@ -161,7 +161,7 @@ // Receive right to which an empty Mach message is sent to wake up the pump // in response to ScheduleWork(). - mac::ScopedMachReceiveRight wakeup_; + apple::ScopedMachReceiveRight wakeup_; // Scratch buffer that is used to receive the message sent to |wakeup_|. mach_msg_empty_rcv_t wakeup_buffer_;
diff --git a/base/message_loop/message_pump_kqueue_unittest.cc b/base/message_loop/message_pump_kqueue_unittest.cc index fb7c3c3..d993fba7 100644 --- a/base/message_loop/message_pump_kqueue_unittest.cc +++ b/base/message_loop/message_pump_kqueue_unittest.cc
@@ -28,16 +28,16 @@ MessagePumpKqueue* pump() { return pump_; } - static void CreatePortPair(mac::ScopedMachReceiveRight* receive, - mac::ScopedMachSendRight* send) { + static void CreatePortPair(apple::ScopedMachReceiveRight* receive, + apple::ScopedMachSendRight* send) { mach_port_options_t options{}; options.flags = MPO_INSERT_SEND_RIGHT; - mac::ScopedMachReceiveRight port; + apple::ScopedMachReceiveRight port; kern_return_t kr = mach_port_construct( mach_task_self(), &options, 0, - mac::ScopedMachReceiveRight::Receiver(*receive).get()); + apple::ScopedMachReceiveRight::Receiver(*receive).get()); ASSERT_EQ(kr, KERN_SUCCESS); - *send = mac::ScopedMachSendRight(receive->get()); + *send = apple::ScopedMachSendRight(receive->get()); } static mach_msg_return_t SendEmptyMessage(mach_port_t remote_port, @@ -79,8 +79,8 @@ }; TEST_F(MessagePumpKqueueTest, MachPortBasicWatch) { - mac::ScopedMachReceiveRight port; - mac::ScopedMachSendRight send_right; + apple::ScopedMachReceiveRight port; + apple::ScopedMachSendRight send_right; CreatePortPair(&port, &send_right); mach_msg_id_t msgid = 'helo'; @@ -110,8 +110,8 @@ } TEST_F(MessagePumpKqueueTest, MachPortStopWatching) { - mac::ScopedMachReceiveRight port; - mac::ScopedMachSendRight send_right; + apple::ScopedMachReceiveRight port; + apple::ScopedMachSendRight send_right; CreatePortPair(&port, &send_right); RunLoop run_loop; @@ -141,8 +141,8 @@ } TEST_F(MessagePumpKqueueTest, MultipleMachWatchers) { - mac::ScopedMachReceiveRight port1, port2; - mac::ScopedMachSendRight send_right1, send_right2; + apple::ScopedMachReceiveRight port1, port2; + apple::ScopedMachSendRight send_right1, send_right2; CreatePortPair(&port1, &send_right1); CreatePortPair(&port2, &send_right2);
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc index c65b7ba..d8f74ef 100644 --- a/base/metrics/field_trial.cc +++ b/base/metrics/field_trial.cc
@@ -1105,7 +1105,7 @@ auto* rendezvous = MachPortRendezvousClient::GetInstance(); if (!rendezvous) return ReadOnlySharedMemoryRegion(); - mac::ScopedMachSendRight scoped_handle = rendezvous->TakeSendRight( + apple::ScopedMachSendRight scoped_handle = rendezvous->TakeSendRight( static_cast<MachPortsForRendezvous::key_type>(field_trial_handle)); if (!scoped_handle.is_valid()) return ReadOnlySharedMemoryRegion();
diff --git a/base/process/process_mac.cc b/base/process/process_mac.cc index 355859cc..4b3bc4a9 100644 --- a/base/process/process_mac.cc +++ b/base/process/process_mac.cc
@@ -14,8 +14,8 @@ #include <iterator> #include <memory> +#include "base/apple/mach_logging.h" #include "base/feature_list.h" -#include "base/mac/mach_logging.h" #include "base/memory/free_deleter.h" #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/base/process/process_metrics_apple.cc b/base/process/process_metrics_apple.cc index 8506c3a1..8ace8e0d 100644 --- a/base/process/process_metrics_apple.cc +++ b/base/process/process_metrics_apple.cc
@@ -11,10 +11,10 @@ #include <stdint.h> #include <sys/sysctl.h> +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" #include "base/logging.h" #include "base/mac/mac_util.h" -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_mach_port.h" #include "base/memory/ptr_util.h" #include "base/numerics/safe_math.h" #include "base/time/time.h" @@ -162,7 +162,7 @@ // Bytes committed by the system. size_t GetSystemCommitCharge() { - base::mac::ScopedMachSendRight host(mach_host_self()); + base::apple::ScopedMachSendRight host(mach_host_self()); mach_msg_type_number_t count = HOST_VM_INFO_COUNT; vm_statistics_data_t data; kern_return_t kr = host_statistics( @@ -178,7 +178,7 @@ bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { struct host_basic_info hostinfo; mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; - base::mac::ScopedMachSendRight host(mach_host_self()); + base::apple::ScopedMachSendRight host(mach_host_self()); int result = host_info(host.get(), HOST_BASIC_INFO, reinterpret_cast<host_info_t>(&hostinfo), &count); if (result != KERN_SUCCESS) { @@ -245,18 +245,18 @@ // The kernel always returns a null object for VM_REGION_TOP_INFO, but // balance it with a deallocate in case this ever changes. See 10.9.2 // xnu-2422.90.20/osfmk/vm/vm_map.c vm_map_region. - mac::ScopedMachSendRight object_name; + apple::ScopedMachSendRight object_name; kern_return_t kr = #if BUILDFLAG(IS_MAC) mach_vm_region(task, address, size, VM_REGION_TOP_INFO, reinterpret_cast<vm_region_info_t>(info), &info_count, - mac::ScopedMachSendRight::Receiver(object_name).get()); + apple::ScopedMachSendRight::Receiver(object_name).get()); #else vm_region_64(task, reinterpret_cast<vm_address_t*>(address), reinterpret_cast<vm_size_t*>(size), VM_REGION_TOP_INFO, reinterpret_cast<vm_region_info_t>(info), &info_count, - mac::ScopedMachSendRight::Receiver(object_name).get()); + apple::ScopedMachSendRight::Receiver(object_name).get()); #endif return ParseOutputFromMachVMRegion(kr); } @@ -269,19 +269,19 @@ // The kernel always returns a null object for VM_REGION_BASIC_INFO_64, but // balance it with a deallocate in case this ever changes. See 10.9.2 // xnu-2422.90.20/osfmk/vm/vm_map.c vm_map_region. - mac::ScopedMachSendRight object_name; + apple::ScopedMachSendRight object_name; kern_return_t kr = #if BUILDFLAG(IS_MAC) mach_vm_region(task, address, size, VM_REGION_BASIC_INFO_64, reinterpret_cast<vm_region_info_t>(info), &info_count, - mac::ScopedMachSendRight::Receiver(object_name).get()); + apple::ScopedMachSendRight::Receiver(object_name).get()); #else vm_region_64(task, reinterpret_cast<vm_address_t*>(address), reinterpret_cast<vm_size_t*>(size), VM_REGION_BASIC_INFO_64, reinterpret_cast<vm_region_info_t>(info), &info_count, - mac::ScopedMachSendRight::Receiver(object_name).get()); + apple::ScopedMachSendRight::Receiver(object_name).get()); #endif return ParseOutputFromMachVMRegion(kr); }
diff --git a/base/process/process_metrics_mac.cc b/base/process/process_metrics_mac.cc index f4b103b..bb627af 100644 --- a/base/process/process_metrics_mac.cc +++ b/base/process/process_metrics_mac.cc
@@ -15,9 +15,9 @@ #include <sys/sysctl.h> #include <memory> +#include "base/apple/mach_logging.h" #include "base/logging.h" #include "base/mac/mac_util.h" -#include "base/mac/mach_logging.h" #include "base/memory/ptr_util.h" #include "base/process/process_metrics_iocounters.h" #include "base/time/time.h"
diff --git a/base/profiler/suspendable_thread_delegate_mac.cc b/base/profiler/suspendable_thread_delegate_mac.cc index a3621458..760a61c 100644 --- a/base/profiler/suspendable_thread_delegate_mac.cc +++ b/base/profiler/suspendable_thread_delegate_mac.cc
@@ -10,8 +10,8 @@ #include <vector> +#include "base/apple/mach_logging.h" #include "base/check.h" -#include "base/mac/mach_logging.h" #include "base/profiler/profile_builder.h" #include "build/build_config.h"
diff --git a/base/synchronization/waitable_event.h b/base/synchronization/waitable_event.h index 0ba6519..744aa9db 100644 --- a/base/synchronization/waitable_event.h +++ b/base/synchronization/waitable_event.h
@@ -19,8 +19,8 @@ #include <list> #include <memory> +#include "base/apple/scoped_mach_port.h" #include "base/functional/callback_forward.h" -#include "base/mac/scoped_mach_port.h" #include "base/memory/ref_counted.h" #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) #include <list> @@ -203,7 +203,7 @@ friend class RefCountedThreadSafe<ReceiveRight>; ~ReceiveRight(); - mac::ScopedMachReceiveRight right_; + apple::ScopedMachReceiveRight right_; }; const ResetPolicy policy_; @@ -214,7 +214,7 @@ // The send right used to signal the event. This can be disposed of with // the event, unlike the receive right, since a deleted event cannot be // signaled. - mac::ScopedMachSendRight send_right_; + apple::ScopedMachSendRight send_right_; #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) // On Windows, you must not close a HANDLE which is currently being waited on. // The MSDN documentation says that the resulting behaviour is 'undefined'.
diff --git a/base/synchronization/waitable_event_apple.cc b/base/synchronization/waitable_event_apple.cc index 55a197d..7d92456 100644 --- a/base/synchronization/waitable_event_apple.cc +++ b/base/synchronization/waitable_event_apple.cc
@@ -10,8 +10,8 @@ #include <limits> #include <memory> +#include "base/apple/mach_logging.h" #include "base/files/scoped_file.h" -#include "base/mac/mach_logging.h" #include "base/notreached.h" #include "base/posix/eintr_wrapper.h" #include "base/threading/scoped_blocking_call.h" @@ -178,7 +178,7 @@ kern_return_t kr; - mac::ScopedMachPortSet port_set; + apple::ScopedMachPortSet port_set; { mach_port_t name; kr =
diff --git a/base/synchronization/waitable_event_watcher_mac.cc b/base/synchronization/waitable_event_watcher_mac.cc index f9bc3aa..c764740 100644 --- a/base/synchronization/waitable_event_watcher_mac.cc +++ b/base/synchronization/waitable_event_watcher_mac.cc
@@ -4,9 +4,9 @@ #include "base/synchronization/waitable_event_watcher.h" +#include "base/apple/scoped_dispatch_object.h" #include "base/functional/bind.h" #include "base/functional/callback.h" -#include "base/mac/scoped_dispatch_object.h" namespace base {
diff --git a/base/system/sys_info_ios.mm b/base/system/sys_info_ios.mm index ed91821..e74ecd7 100644 --- a/base/system/sys_info_ios.mm +++ b/base/system/sys_info_ios.mm
@@ -11,8 +11,8 @@ #include <sys/sysctl.h> #include <sys/types.h> +#include "base/apple/scoped_mach_port.h" #include "base/check_op.h" -#include "base/mac/scoped_mach_port.h" #include "base/notreached.h" #include "base/numerics/safe_conversions.h" #include "base/process/process_metrics.h" @@ -110,7 +110,7 @@ uint64_t SysInfo::AmountOfPhysicalMemoryImpl() { struct host_basic_info hostinfo; mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; - base::mac::ScopedMachSendRight host(mach_host_self()); + base::apple::ScopedMachSendRight host(mach_host_self()); int result = host_info(host.get(), HOST_BASIC_INFO, reinterpret_cast<host_info_t>(&hostinfo), &count); if (result != KERN_SUCCESS) {
diff --git a/base/system/sys_info_mac.mm b/base/system/sys_info_mac.mm index 22094e6..5a33613 100644 --- a/base/system/sys_info_mac.mm +++ b/base/system/sys_info_mac.mm
@@ -12,11 +12,11 @@ #include <sys/sysctl.h> #include <sys/types.h> +#include "base/apple/scoped_mach_port.h" #include "base/check_op.h" #include "base/debug/stack_trace.h" #include "base/feature_list.h" #include "base/mac/mac_util.h" -#include "base/mac/scoped_mach_port.h" #include "base/no_destructor.h" #include "base/notreached.h" #include "base/numerics/safe_conversions.h" @@ -113,7 +113,7 @@ uint64_t SysInfo::AmountOfPhysicalMemoryImpl() { struct host_basic_info hostinfo; mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; - base::mac::ScopedMachSendRight host(mach_host_self()); + base::apple::ScopedMachSendRight host(mach_host_self()); int result = host_info(host.get(), HOST_BASIC_INFO, reinterpret_cast<host_info_t>(&hostinfo), &count); if (result != KERN_SUCCESS) {
diff --git a/base/task/thread_pool/worker_thread.cc b/base/task/thread_pool/worker_thread.cc index 84fb4db..09dbf290 100644 --- a/base/task/thread_pool/worker_thread.cc +++ b/base/task/thread_pool/worker_thread.cc
@@ -34,7 +34,7 @@ #endif #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \ @@ -442,7 +442,7 @@ bool got_work_this_wakeup = false; while (!ShouldExit()) { #if BUILDFLAG(IS_APPLE) - mac::ScopedNSAutoreleasePool autorelease_pool; + apple::ScopedNSAutoreleasePool autorelease_pool; #endif absl::optional<WatchHangsInScope> hang_watch_scope; if (watch_for_hangs)
diff --git a/base/test/launcher/test_launcher.cc b/base/test/launcher/test_launcher.cc index c0dbdcca..7f79116 100644 --- a/base/test/launcher/test_launcher.cc +++ b/base/test/launcher/test_launcher.cc
@@ -74,7 +74,7 @@ #endif #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif #if BUILDFLAG(IS_WIN)
diff --git a/base/test/test_suite.cc b/base/test/test_suite.cc index 057514f..f0157aa5 100644 --- a/base/test/test_suite.cc +++ b/base/test/test_suite.cc
@@ -58,7 +58,7 @@ #include "testing/multiprocess_func_list.h" #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif // BUILDFLAG(IS_APPLE) #if BUILDFLAG(IS_IOS) @@ -363,7 +363,7 @@ // Initialize(). See bug 6436. int TestSuite::Run() { #if BUILDFLAG(IS_APPLE) - mac::ScopedNSAutoreleasePool scoped_pool; + apple::ScopedNSAutoreleasePool scoped_pool; #endif std::string client_func =
diff --git a/base/threading/platform_thread_apple.mm b/base/threading/platform_thread_apple.mm index 36af5c29..83c6e37 100644 --- a/base/threading/platform_thread_apple.mm +++ b/base/threading/platform_thread_apple.mm
@@ -15,12 +15,12 @@ #include <algorithm> #include <atomic> +#include "base/apple/mach_logging.h" #include "base/feature_list.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/mac/foundation_util.h" #include "base/mac/mac_util.h" -#include "base/mac/mach_logging.h" #include "base/metrics/histogram_functions.h" #include "base/threading/thread_id_name_manager.h" #include "base/threading/threading_features.h"
diff --git a/base/time/time_apple.mm b/base/time/time_apple.mm index 91ade2b..e9babe7 100644 --- a/base/time/time_apple.mm +++ b/base/time/time_apple.mm
@@ -14,10 +14,10 @@ #include <sys/types.h> #include <time.h> +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" #include "base/logging.h" -#include "base/mac/mach_logging.h" #include "base/mac/scoped_cftyperef.h" -#include "base/mac/scoped_mach_port.h" #include "base/numerics/safe_conversions.h" #include "base/time/time_override.h" #include "build/build_config.h"
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni index d2f20d70..1e42bf0 100644 --- a/build/config/android/rules.gni +++ b/build/config/android/rules.gni
@@ -4993,9 +4993,9 @@ _module_target = _module.module_target _module_build_config = _module.build_config _module_build_config_target = _module.build_config_target - _module_target_name = get_label_info(_module_target, "name") if (!_proguard_enabled) { + _module_target_name = get_label_info(_module_target, "name") _dex_target = "${_module_target_name}__final_dex" _dex_path = "$target_out_dir/$_module_target_name/$_module_target_name.mergeddex.jar" dex(_dex_target) { @@ -5017,6 +5017,7 @@ _dex_target_for_module = ":$_dex_target" if (_enable_art_profile_optimizations && _include_baseline_profile) { + _module_target_name = get_label_info(_module_target, "name") _binary_profile_target = "${_module_target_name}__binary_baseline_profile" _binary_baseline_profile_path = "$target_out_dir/$_module_target_name/$_module_target_name.baseline.prof"
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn index f1924c2e..8d95147 100644 --- a/chrome/BUILD.gn +++ b/chrome/BUILD.gn
@@ -177,7 +177,7 @@ if (is_chromeos_ash) { data_deps += [ - "//components/variations/cros:evaluate_seed", + "//components/variations/cros_evaluate_seed:evaluate_seed", "//sandbox/linux:chrome_sandbox", ] deps += [ "//components/exo/wayland:ui_controls_protocol_stub" ]
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 7cb82d3..da5c414 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
@@ -315,7 +315,8 @@ mHasEmptyView = hasEmptyView; if (mHasEmptyView) { - mTabListEmptyCoordinator = new TabListEmptyCoordinator(parentView, mModel); + mTabListEmptyCoordinator = + new TabListEmptyCoordinator(parentView, mModel, browserControlsStateProvider); mEmptyStateHeadingResId = emptyHeadingStringResId; mEmptyStateSubheadingResId = emptySubheadingStringResId; mEmptyStateImageResId = emptyImageResId;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEmptyCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEmptyCoordinator.java index d5ab690..995dba19 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEmptyCoordinator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEmptyCoordinator.java
@@ -7,9 +7,11 @@ import android.content.Context; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; +import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider; import org.chromium.chrome.tab_ui.R; import org.chromium.ui.modelutil.ListObservable; import org.chromium.ui.modelutil.ListObservable.ListObserver; @@ -30,13 +32,16 @@ private ListObserver<Void> mListObserver; private boolean mIsTabSwitcherShowing; private boolean mIsListObserverAttached; + private BrowserControlsStateProvider mBrowserControlsStateProvider; - public TabListEmptyCoordinator(ViewGroup rootView, TabListModel model) { + public TabListEmptyCoordinator(ViewGroup rootView, TabListModel model, + BrowserControlsStateProvider browserControlsStateProvider) { mRootView = rootView; mContext = rootView.getContext(); // Observe TabListModel to determine when to add / remove empty state view. mModel = model; + mBrowserControlsStateProvider = browserControlsStateProvider; mListObserver = new ListObserver<Void>() { @Override public void onItemRangeInserted(ListObservable source, int index, int count) { @@ -118,6 +123,11 @@ public void attachEmptyView() { if (mEmptyView != null && mEmptyView.getParent() == null) { mRootView.addView(mEmptyView); + int toolbarHeightPx = mBrowserControlsStateProvider.getTopControlsHeight(); + FrameLayout.LayoutParams emptyViewParams = + (FrameLayout.LayoutParams) mEmptyView.getLayoutParams(); + emptyViewParams.topMargin = toolbarHeightPx; + mEmptyView.setLayoutParams(emptyViewParams); } setEmptyViewVisibility(View.GONE); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java index 73e551f8..3650c18 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -22,6 +22,7 @@ import android.view.ViewStub; import android.widget.FrameLayout; +import androidx.activity.BackEventCompat; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -67,6 +68,7 @@ import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.fullscreen.FullscreenManager; import org.chromium.chrome.browser.fullscreen.FullscreenOptions; +import org.chromium.chrome.browser.gesturenav.TabOnBackGestureHandler; import org.chromium.chrome.browser.history.HistoryManagerUtils; import org.chromium.chrome.browser.homepage.HomepageManager; import org.chromium.chrome.browser.homepage.HomepagePolicyManager; @@ -157,6 +159,7 @@ import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; import org.chromium.components.browser_ui.styles.ChromeColors; import org.chromium.components.browser_ui.widget.gesture.BackPressHandler; +import org.chromium.components.browser_ui.widget.gesture.BackPressHandler.BackPressResult; import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightParams; import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightShape; import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator; @@ -174,6 +177,7 @@ import org.chromium.content_public.browser.NavigationHandle; import org.chromium.content_public.browser.WebContents; import org.chromium.net.NetError; +import org.chromium.ui.base.BackGestureEventSwipeEdge; import org.chromium.ui.base.DeviceFormFactor; import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.WindowAndroid; @@ -190,7 +194,7 @@ */ public class ToolbarManager implements UrlFocusChangeListener, ThemeColorObserver, TintObserver, MenuButtonDelegate, ChromeAccessibilityUtil.Observer, - TabObscuringHandler.Observer, BackPressHandler { + TabObscuringHandler.Observer { private final IncognitoStateProvider mIncognitoStateProvider; private final TabCountProvider mTabCountProvider; private final TopUiThemeColorProvider mTopUiThemeColorProvider; @@ -383,6 +387,47 @@ } } + private class OnBackPressHandler implements BackPressHandler { + private TabOnBackGestureHandler mHandler; + + @Override + public int handleBackPress() { + int res = ToolbarManager.this.handleBackPress(); + mHandler.onBackInvoked(); + return res; + } + + @Override + public ObservableSupplier<Boolean> getHandleBackPressChangedSupplier() { + return ToolbarManager.this.mBackPressStateSupplier; + } + + @Override + public void handleOnBackCancelled() { + mHandler.onBackCancelled(); + } + + @Override + public void handleOnBackProgressed(@NonNull BackEventCompat backEvent) { + mHandler.onBackProgressed(backEvent.getTouchX(), backEvent.getTouchY(), + backEvent.getProgress(), + backEvent.getSwipeEdge() == BackEventCompat.EDGE_LEFT + ? BackGestureEventSwipeEdge.LEFT + : BackGestureEventSwipeEdge.RIGHT); + } + + @Override + public void handleOnBackStarted(@NonNull BackEventCompat backEvent) { + mHandler = TabOnBackGestureHandler.from(mActivityTabProvider.get()); + mHandler.onBackStarted(backEvent.getTouchX(), backEvent.getTouchY(), + backEvent.getProgress(), + backEvent.getSwipeEdge() == BackEventCompat.EDGE_LEFT + ? BackGestureEventSwipeEdge.LEFT + : BackGestureEventSwipeEdge.RIGHT, + false); + } + } + /** * Creates a ToolbarManager object. * @@ -566,7 +611,8 @@ this::updateButtonStatus, mActivityTabProvider); // clang-format on if (backPressManager != null && BackPressManager.isEnabled()) { - backPressManager.addHandler(this, BackPressHandler.Type.TAB_HISTORY); + OnBackPressHandler handler = new OnBackPressHandler(); + backPressManager.addHandler(handler, BackPressHandler.Type.TAB_HISTORY); mLastBackPressMsSupplier = backPressManager::getLastPressMs; } @@ -2203,7 +2249,6 @@ mBackPressStateSupplier.set(tab != null && mToolbarTabController.canGoBack()); } - @Override public @BackPressResult int handleBackPress() { boolean ret = back(); if (!ret) { @@ -2230,7 +2275,6 @@ return ret ? BackPressResult.SUCCESS : BackPressResult.FAILURE; } - @Override public ObservableSupplier<Boolean> getHandleBackPressChangedSupplier() { return mBackPressStateSupplier; }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/ChromiumAndroidLinkerMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/ChromiumAndroidLinkerMetricsTest.java index ee20f708..0e950e39 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/ChromiumAndroidLinkerMetricsTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/ChromiumAndroidLinkerMetricsTest.java
@@ -13,7 +13,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.CommandLineFlags; import org.chromium.chrome.browser.flags.ChromeSwitches; @@ -31,7 +30,6 @@ @Batch(Batch.PER_CLASS) @CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE) public class ChromiumAndroidLinkerMetricsTest { - private static final String BROWSER_HISTOGRAM = "ChromiumAndroidLinker.BrowserLoadTime2"; private static final String PAGE_PREFIX = "/chrome/test/data/android/google.html"; @Rule @@ -69,7 +67,6 @@ Assert.assertTrue("First Contentful Paint must be reported", metricsObserver.waitForFirstContentfulPaintEvent()); - Assert.assertEquals(1, RecordHistogram.getHistogramTotalCountForTesting(BROWSER_HISTOGRAM)); // Not testing the histogram from non-main process because the values can be stale. mActivityTestRule.loadUrl(getNextLoadUrl());
diff --git a/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/ExpandedPlayer.java b/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/ExpandedPlayer.java index 70201ee7..32d5da9 100644 --- a/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/ExpandedPlayer.java +++ b/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/ExpandedPlayer.java
@@ -8,19 +8,32 @@ /** Interface for controlling the Read Aloud expanded player. */ public interface ExpandedPlayer { + /** Interface for getting updates about the expanded player. */ + public interface Observer { + /** Called when the user has tapped the close button. */ + void onCloseClicked(); + } + /** - * Bind the player to a Playback object. - * @param playback Playback object. + * Add an observer. + * @param observer Observer to add. */ - default void setPlayback(Playback playback) {} + default void addObserver(Observer observer) {} + + /** + * Remove an observer. Has no effect if `observer` wasn't previously added. + * @param observer Observer to remove. + */ + default void removeObserver(Observer observer) {} /** * Show the expanded player. * * If current state is GONE or HIDING, switch to SHOWING. No effect if state is * VISIBLE or SHOWING. + * @param playback Current playback object. Should not be null. */ - default void show() {} + default void show(Playback playback) {} /** * Dismiss the expanded player.
diff --git a/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/Playback.java b/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/Playback.java index e8a9444..8eae53be 100644 --- a/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/Playback.java +++ b/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/Playback.java
@@ -4,9 +4,74 @@ package org.chromium.chrome.modules.readaloud; +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** Represents a single audio playback session. */ public interface Playback { /** + * Metadata describing the content of the playback + */ + interface Metadata { + String languageCode(); + String title(); + String publisher(); + String author(); + String fullText(); + TextPart[] paragraphs(); + long estimatedDurationSeconds(); + String canonicalUrl(); + } + + /** + * Represents a single text part. + * This might be a paragraph, a sentence or any semantic unit of the article. + */ + interface TextPart { + // The index of the text part's paragraph in the full article. + int getParagraphIndex(); + // The offset of the text part in the full text. + int getOffset(); + // The length of the text part, in characters. + int getLength(); + + @TextType + int getType(); + } + + /** + * Type of a text portion. + */ + @IntDef({TextType.TEXT_TYPE_UNSPECIFIED, TextType.TEXT_TYPE_NORMAL, TextType.TEXT_TYPE_TITLE, + TextType.TEXT_TYPE_PUBLISHER_AND_AUTHOR, TextType.TEXT_TYPE_PUBLISHER, + TextType.TEXT_TYPE_AUTHOR}) + @Retention(RetentionPolicy.SOURCE) + @interface TextType { + // Unspecified. + int TEXT_TYPE_UNSPECIFIED = 0; + // Normal text (without a particular role). + int TEXT_TYPE_NORMAL = 1; + // The title of a document. + int TEXT_TYPE_TITLE = 2; + // The publisher and author part of the document. + int TEXT_TYPE_PUBLISHER_AND_AUTHOR = 3; + // The publisher or the document. + int TEXT_TYPE_PUBLISHER = 4; + // The author of the document. + int TEXT_TYPE_AUTHOR = 5; + } + + /** + * Returns the metadata represented by this playback. + * @return Metadata with language, title, publisher, full text, etc. + */ + default Metadata getMetadata() { + return null; + } + + /** * Add a listener to be called on playback events. * @param listener Listener. */
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index e764356..3383f09 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd
@@ -10529,14 +10529,14 @@ </if> </if> <!-- not is_android --> - <if expr="chromeos_ash"> + <if expr="is_chromeos"> <message name="IDS_ARC_LABEL" desc="Label for ARC"> ARC </message> <message name="IDS_PLATFORM_LABEL" desc="Label for Platform"> Platform </message> - </if> <!-- chromeos_ash --> + </if> <!-- is_chromeos --> <!-- Settings related strings not specific to chrome://settings. (Settings specific strings are in settings_strings.grdp).
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp index f4dbd23..574fbd7f 100644 --- a/chrome/app/os_settings_strings.grdp +++ b/chrome/app/os_settings_strings.grdp
@@ -5140,9 +5140,15 @@ <message name="IDS_SETTINGS_DISPLAY_REFRESH_RATE_TITLE" desc="In Device Settings > Displays, the label for the section for changing a display's refresh rate."> Refresh Rate </message> + <message name="IDS_OS_SETTINGS_REVAMP_DISPLAY_REFRESH_RATE_TITLE" desc="In Device Settings > Displays, the label for the section for changing a display's refresh rate."> + Refresh rate + </message> <message name="IDS_SETTINGS_DISPLAY_REFRESH_RATE_SUBLABEL" desc="In Device Settings > Displays, the text describing the drop down menu to select the desired refresh rate of the selected external display."> Determines the frequency that the screen updates </message> + <message name="IDS_OS_SETTINGS_REVAMP_DISPLAY_REFRESH_RATE_DESCRIPTION" desc="In Device Settings > Displays, the text describing the drop down menu to select the desired refresh rate of the selected external display."> + With a higher refresh rate, you'll have a smoother display with more details. Increased refresh rate may impact battery life. + </message> <message name="IDS_SETTINGS_DISPLAY_REFRESH_RATE_MENU_ITEM" desc="In Device Settings > Displays, the text entry for a single item in the external display refresh rate drop down menu, when Resolution and Refresh Rate are displayed separately. Hz is the SI unit Hertz"> <ph name="REFRESH_RATE">$1<ex>60</ex></ph> Hz </message> @@ -5152,9 +5158,15 @@ <message name="IDS_SETTINGS_DISPLAY_ZOOM_TITLE" desc="In Device Settings > Displays, the title for the section for changing the display's zoom."> Display size </message> + <message name="IDS_OS_SETTINGS_REVAMP_DISPLAY_ZOOM_LABEL" desc="In Device Settings > Displays, the label for the section for changing the display's zoom."> + Display and text size + </message> <message name="IDS_SETTINGS_DISPLAY_ZOOM_SUBLABEL" desc="In Device Settings > Displays, the text describing the display's zoom."> Make items on your screen smaller or larger </message> + <message name="IDS_OS_SETTINGS_REVAMP_DISPLAY_ZOOM_DESCRIPTION" desc="In Device Settings > Displays, the description for the display's zoom."> + Make items on your screen, including text, smaller or larger + </message> <message name="IDS_SETTINGS_DISPLAY_ZOOM_VALUE" desc="The currently selected display zoom percentage."> <ph name="DISPLAY_ZOOM">$1<ex>120</ex></ph>% </message> @@ -5197,9 +5209,15 @@ <message name="IDS_SETTINGS_DISPLAY_OVERSCAN_TITLE" desc="Title of the settings subpage which adjusts display overscan."> Overscan </message> + <message name="IDS_OS_SETTINGS_REVAMP_DISPLAY_BOUNDARIES_TITLE" desc="Title of the settings subpage which adjusts display boundaries."> + Display boundaries + </message> <message name="IDS_SETTINGS_DISPLAY_OVERSCAN_SUBTITLE" desc="Subtitle for the display overscan settings subpage."> Adjust the boundaries of your display </message> + <message name="IDS_OS_SETTINGS_REVAMP_DISPLAY_BOUNDARIES_DESCRIPTION" desc="Description for the display boundaries settings subpage."> + Press the arrow keys to shrink or expand the display area. To move the display area around, press shift and +, then use the arrow keys. + </message> <message name="IDS_SETTINGS_DISPLAY_OVERSCAN_INSTRUCTIONS" desc="Instructions for changing the display overscan calibration."> Tap the following keys to adjust or move the cropping area </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_BOUNDARIES_DESCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_BOUNDARIES_DESCRIPTION.png.sha1 new file mode 100644 index 0000000..2333a7d --- /dev/null +++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_BOUNDARIES_DESCRIPTION.png.sha1
@@ -0,0 +1 @@ +9f82cbdc74da67b0c526cdd7640105df476f82d1 \ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_BOUNDARIES_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_BOUNDARIES_TITLE.png.sha1 new file mode 100644 index 0000000..7001671 --- /dev/null +++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_BOUNDARIES_TITLE.png.sha1
@@ -0,0 +1 @@ +dcf6449590ffec6cf8d78685e505ef1ffc85f56e \ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_REFRESH_RATE_DESCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_REFRESH_RATE_DESCRIPTION.png.sha1 new file mode 100644 index 0000000..85c36214 --- /dev/null +++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_REFRESH_RATE_DESCRIPTION.png.sha1
@@ -0,0 +1 @@ +f7f57fc055ded34ca1245dba24330d5512e48382 \ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_REFRESH_RATE_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_REFRESH_RATE_TITLE.png.sha1 new file mode 100644 index 0000000..f6f7430 --- /dev/null +++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_REFRESH_RATE_TITLE.png.sha1
@@ -0,0 +1 @@ +5e9f81a96703b5749b84c514d1c38fd5bf4cbbd8 \ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_ZOOM_DESCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_ZOOM_DESCRIPTION.png.sha1 new file mode 100644 index 0000000..b0c5e6b --- /dev/null +++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_ZOOM_DESCRIPTION.png.sha1
@@ -0,0 +1 @@ +290eca75a97c927dac3279ae0f67105853657fd9 \ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_ZOOM_LABEL.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_ZOOM_LABEL.png.sha1 new file mode 100644 index 0000000..7e8620f --- /dev/null +++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_REVAMP_DISPLAY_ZOOM_LABEL.png.sha1
@@ -0,0 +1 @@ +5abd958375b81218993e837694afae673bf8b873 \ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp index a4a5e67..3f4c3739 100644 --- a/chrome/app/settings_strings.grdp +++ b/chrome/app/settings_strings.grdp
@@ -2564,9 +2564,6 @@ <message name="IDS_SETTINGS_SAFETY_CHECK_TOAST_UNDO_BUTTON_LABEL" desc="After the user has modified permissions for a website, a toast popup message is shown with this text next to a description of the action taken. The user can click this text to undo the action. For example, this text would be next to a descriptiton that says 'Allowed notification permissions for example.com'."> Undo </message> - <message name="IDS_SETTINGS_NETWORK_PREDICTION_ENABLED_LABEL" desc="In the advanced options tab, the text next to the checkbox that enables prediction of network actions. Actions include browser-initiated DNS prefetching, TCP and SSL preconnection, and prerendering of webpages."> - Preload pages for faster browsing and searching - </message> <message name="IDS_SETTINGS_NETWORK_PREDICTION_ENABLED_DESC" desc="In the advanced options tab, the secondary text next to the checkbox that enables prediction of network actions."> Uses cookies to remember your preferences, even if you don’t visit those pages </message>
diff --git a/chrome/app_shim/app_shim_controller.mm b/chrome/app_shim/app_shim_controller.mm index c3a8c93..fc044518 100644 --- a/chrome/app_shim/app_shim_controller.mm +++ b/chrome/app_shim/app_shim_controller.mm
@@ -10,6 +10,7 @@ #include <utility> #include "base/apple/bundle_locations.h" +#include "base/apple/mach_logging.h" #include "base/base_switches.h" #include "base/command_line.h" #include "base/files/file_util.h" @@ -18,7 +19,6 @@ #include "base/mac/foundation_util.h" #include "base/mac/launch_application.h" #include "base/mac/mac_util.h" -#include "base/mac/mach_logging.h" #include "base/memory/raw_ptr.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h"
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index a27fa0a..db89773d 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -301,8 +301,6 @@ "commerce/shopping_service_factory.h", "complex_tasks/task_tab_helper.cc", "complex_tasks/task_tab_helper.h", - "component_updater/chrome_client_side_phishing_component_installer.cc", - "component_updater/chrome_client_side_phishing_component_installer.h", "component_updater/chrome_component_updater_configurator.cc", "component_updater/chrome_component_updater_configurator.h", "component_updater/chrome_origin_trials_component_installer.cc", @@ -387,8 +385,6 @@ "dips/dips_cleanup_service_factory.h", "dips/dips_database.cc", "dips/dips_database.h", - "dips/dips_features.cc", - "dips/dips_features.h", "dips/dips_redirect_info.cc", "dips/dips_redirect_info.h", "dips/dips_service.cc", @@ -5299,7 +5295,6 @@ "//chromeos/strings", "//chromeos/ui/base", "//chromeos/ui/vector_icons", - "//chromeos/version", "//components/account_manager_core", "//components/app_constants", "//components/app_restore", @@ -5734,6 +5729,7 @@ "//chromeos/dbus/power", "//chromeos/ui/clipboard_history", "//chromeos/ui/wm", + "//chromeos/version", "//components/app_constants", "//components/arc/common", "//components/arc/common:arc_intent_helper_constants", @@ -5761,6 +5757,7 @@ } if (is_mac) { sources += [ + "webauthn/chrome_authenticator_request_delegate_mac.mm", "webauthn/local_credential_management_mac.cc", "webauthn/local_credential_management_mac.h", ]
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 7a4f5ec7..930d403 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -30,11 +30,11 @@ #include "build/build_config.h" #include "cc/base/features.h" #include "cc/base/switches.h" +#include "chrome/browser/apps/app_discovery_service/app_discovery_service.h" #include "chrome/browser/browser_features.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/companion/core/features.h" #include "chrome/browser/companion/visual_search/features.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/fast_checkout/fast_checkout_features.h" #include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h" #include "chrome/browser/flag_descriptions.h" @@ -9160,7 +9160,7 @@ {"bounce-tracking-mitigations", flag_descriptions::kDIPSName, flag_descriptions::kDIPSDescription, kOsAll, - FEATURE_WITH_PARAMS_VALUE_TYPE(dips::kFeature, kDIPSVariations, "DIPS")}, + FEATURE_WITH_PARAMS_VALUE_TYPE(features::kDIPS, kDIPSVariations, "DIPS")}, #if BUILDFLAG(IS_CHROMEOS_ASH) {kBorealisBigGlInternalName, flag_descriptions::kBorealisBigGlName, @@ -10803,6 +10803,12 @@ FEATURE_VALUE_TYPE(ash::features::kFloatingWorkspaceV2)}, #endif +#if BUILDFLAG(IS_CHROMEOS_ASH) + {"almanac-game-migration", flag_descriptions::kAlmanacGameMigrationName, + flag_descriptions::kAlmanacGameMigrationDescription, kOsCrOS, + FEATURE_VALUE_TYPE(apps::kAlmanacGameMigration)}, +#endif + // 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/android/send_tab_to_self/send_tab_to_self_android_bridge.cc b/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.cc index 92445879..104849d 100644 --- a/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.cc +++ b/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.cc
@@ -15,7 +15,6 @@ #include "chrome/browser/profiles/profile_android.h" #include "chrome/browser/send_tab_to_self/receiving_ui_handler_registry.h" #include "chrome/browser/sync/send_tab_to_self_sync_service_factory.h" -#include "chrome/browser/sync/sync_service_factory.h" #include "components/send_tab_to_self/entry_point_display_reason.h" #include "components/send_tab_to_self/send_tab_to_self_model.h" #include "components/send_tab_to_self/send_tab_to_self_sync_service.h" @@ -133,13 +132,13 @@ JNIEnv* env, const JavaParamRef<jobject>& j_profile, const JavaParamRef<jstring>& j_url_to_share) { - Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile); + send_tab_to_self::SendTabToSelfSyncService* service = + SendTabToSelfSyncServiceFactory::GetForProfile( + ProfileAndroid::FromProfileAndroid(j_profile)); absl::optional<send_tab_to_self::EntryPointDisplayReason> reason = - send_tab_to_self::GetEntryPointDisplayReason( - GURL(ConvertJavaStringToUTF8(env, j_url_to_share)), - SyncServiceFactory::GetForProfile(profile), - SendTabToSelfSyncServiceFactory::GetForProfile(profile), - profile->GetPrefs()); + service ? service->GetEntryPointDisplayReason( + GURL(ConvertJavaStringToUTF8(env, j_url_to_share))) + : absl::nullopt; if (!reason) { return nullptr;
diff --git a/chrome/browser/apps/app_discovery_service/app_discovery_service.cc b/chrome/browser/apps/app_discovery_service/app_discovery_service.cc index 21d40e69..0994f6fa 100644 --- a/chrome/browser/apps/app_discovery_service/app_discovery_service.cc +++ b/chrome/browser/apps/app_discovery_service/app_discovery_service.cc
@@ -11,6 +11,10 @@ namespace apps { +BASE_FEATURE(kAlmanacGameMigration, + "AlmanacGameMigration", + base::FEATURE_DISABLED_BY_DEFAULT); + AppDiscoveryService::AppDiscoveryService(Profile* profile) : app_fetcher_manager_(std::make_unique<AppFetcherManager>(profile)) {}
diff --git a/chrome/browser/apps/app_discovery_service/app_discovery_service.h b/chrome/browser/apps/app_discovery_service/app_discovery_service.h index 896e6bb9..eb7546ba0 100644 --- a/chrome/browser/apps/app_discovery_service/app_discovery_service.h +++ b/chrome/browser/apps/app_discovery_service/app_discovery_service.h
@@ -8,6 +8,7 @@ #include <memory> #include "base/callback_list.h" +#include "base/feature_list.h" #include "chrome/browser/apps/app_discovery_service/app_discovery_util.h" #include "components/keyed_service/core/keyed_service.h" @@ -17,6 +18,9 @@ class AppFetcherManager; +// Enables App Discovery Service to use the Almanac system for fetching games. +BASE_DECLARE_FEATURE(kAlmanacGameMigration); + // API for consumers to use to fetch apps. class AppDiscoveryService : public KeyedService { public:
diff --git a/chrome/browser/apps/app_shim/mach_bootstrap_acceptor.cc b/chrome/browser/apps/app_shim/mach_bootstrap_acceptor.cc index 8f83f3f..f375e17 100644 --- a/chrome/browser/apps/app_shim/mach_bootstrap_acceptor.cc +++ b/chrome/browser/apps/app_shim/mach_bootstrap_acceptor.cc
@@ -10,8 +10,8 @@ #include <memory> #include <utility> +#include "base/apple/mach_logging.h" #include "base/mac/foundation_util.h" -#include "base/mac/mach_logging.h" #include "base/mac/scoped_mach_msg_destroy.h" #include "base/strings/stringprintf.h" #include "chrome/common/mac/app_mode_common.h" @@ -80,7 +80,7 @@ pid_t sender_pid = audit_token_to_pid(request.trailer.msgh_audit); mojo::PlatformChannelEndpoint remote_endpoint(mojo::PlatformHandle( - base::mac::ScopedMachSendRight(request.header.msgh_remote_port))); + base::apple::ScopedMachSendRight(request.header.msgh_remote_port))); if (!remote_endpoint.is_valid()) { return; }
diff --git a/chrome/browser/ash/extensions/external_cache_impl.cc b/chrome/browser/ash/extensions/external_cache_impl.cc index 901be15..eaa014c6 100644 --- a/chrome/browser/ash/extensions/external_cache_impl.cc +++ b/chrome/browser/ash/extensions/external_cache_impl.cc
@@ -34,7 +34,6 @@ #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/updater/extension_downloader.h" #include "extensions/browser/updater/extension_downloader_types.h" #include "extensions/common/extension.h"
diff --git a/chrome/browser/ash/extensions/install_limiter.cc b/chrome/browser/ash/extensions/install_limiter.cc index 30252cb..1bf8ba1 100644 --- a/chrome/browser/ash/extensions/install_limiter.cc +++ b/chrome/browser/ash/extensions/install_limiter.cc
@@ -11,7 +11,6 @@ #include "base/task/thread_pool.h" #include "chrome/browser/ash/extensions/install_limiter_factory.h" #include "extensions/browser/extensions_browser_client.h" -#include "extensions/browser/notification_types.h" namespace {
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service.cc b/chrome/browser/ash/floating_workspace/floating_workspace_service.cc index 3cf8ed7..e730016 100644 --- a/chrome/browser/ash/floating_workspace/floating_workspace_service.cc +++ b/chrome/browser/ash/floating_workspace/floating_workspace_service.cc
@@ -269,8 +269,7 @@ static_cast<int>( RestoreFromErrorNotificationButtonIndex::kRestore)) { VLOG(1) << "Restore button clicked for floating workspace after error"; - LaunchFloatingWorkspaceTemplate(GetLatestFloatingWorkspaceTemplate(), - /*launch_on_active_desk=*/true); + LaunchFloatingWorkspaceTemplate(GetLatestFloatingWorkspaceTemplate()); } break; } @@ -490,13 +489,11 @@ should_run_restore_ = false; return; } - LaunchFloatingWorkspaceTemplate(desk_template, - /*launch_on_active_desk=*/false); + LaunchFloatingWorkspaceTemplate(desk_template); } void FloatingWorkspaceService::LaunchFloatingWorkspaceTemplate( - const DeskTemplate* desk_template, - bool launch_on_active_desk) { + const DeskTemplate* desk_template) { should_run_restore_ = false; if (desk_template == nullptr) { return; @@ -507,23 +504,12 @@ RemoveAllPreviousDesksExceptActiveDesk( /*exclude_desk_uuid=*/active_desk_uuid); - // If the user clicks on the restore button for launch, launch the apps to the - // current active desk. If it is an auto launch, launch into a new desk and - // remove all other desks after launch. - if (launch_on_active_desk) { - VLOG(1) << "Combining Floating Workspace apps to current desk."; - std::unique_ptr<DeskTemplate> template_copy = desk_template->Clone(); - // Open the apps from the floating workspace on top of existing windows. - saved_desk_util::UpdateTemplateActivationIndices(*template_copy); - GetDesksClient()->LaunchAppsFromTemplate(std::move(template_copy)); - RecordLaunchSavedDeskHistogram(DeskTemplateType::kFloatingWorkspace); - } else { - GetDesksClient()->LaunchDeskTemplate( - desk_template->uuid(), - base::BindOnce(&FloatingWorkspaceService::OnTemplateLaunched, - weak_pointer_factory_.GetWeakPtr()), - desk_template->template_name()); - } + VLOG(1) << "Combining Floating Workspace apps to current desk."; + std::unique_ptr<DeskTemplate> template_copy = desk_template->Clone(); + // Open the apps from the floating workspace on top of existing windows. + saved_desk_util::UpdateTemplateActivationIndices(*template_copy); + GetDesksClient()->LaunchAppsFromTemplate(std::move(template_copy)); + RecordLaunchSavedDeskHistogram(DeskTemplateType::kFloatingWorkspace); } DesksClient* FloatingWorkspaceService::GetDesksClient() { @@ -574,52 +560,6 @@ return true; } -void FloatingWorkspaceService::HandleTemplateLaunchErrors( - DesksClient::DeskActionError error) { - SendNotification(kNotificationForSyncErrorOrTimeOut); - switch (error) { - case DesksClient::DeskActionError::kUnknownError: - floating_workspace_metrics_util:: - RecordFloatingWorkspaceV2TemplateLaunchFailureType( - floating_workspace_metrics_util::LaunchTemplateFailureType:: - kUnknownError); - LOG(WARNING) << "Failed to launch template: unknown error."; - return; - case DesksClient::DeskActionError::kStorageError: - floating_workspace_metrics_util:: - RecordFloatingWorkspaceV2TemplateLaunchFailureType( - floating_workspace_metrics_util::LaunchTemplateFailureType:: - kStorageError); - LOG(WARNING) << "Failed to launch template: storage error."; - return; - case DesksClient::DeskActionError::kDesksCountCheckFailedError: - floating_workspace_metrics_util:: - RecordFloatingWorkspaceV2TemplateLaunchFailureType( - floating_workspace_metrics_util::LaunchTemplateFailureType:: - kDesksCountCheckFailedError); - LOG(WARNING) << "Failed to launch template: max number of desks open."; - return; - // No need to record metrics for the below desk action errors since they - // do not relate to template launch. - case DesksClient::DeskActionError::kNoCurrentUserError: - LOG(WARNING) << "Failed to launch template: no active user."; - return; - case DesksClient::DeskActionError::kBadProfileError: - LOG(WARNING) << "Failed to launch template: bad profile."; - return; - case DesksClient::DeskActionError::kResourceNotFoundError: - LOG(WARNING) << "Failed to launch template: resource not found."; - return; - case DesksClient::DeskActionError::kInvalidIdError: - LOG(WARNING) << "Failed to launch template: desk id is invalid."; - return; - case DesksClient::DeskActionError::kDesksBeingModifiedError: - LOG(WARNING) - << "Failed to launch template: desk is currently being modified."; - return; - } -} - void FloatingWorkspaceService::HandleTemplateCaptureErrors( DesksClient::DeskActionError error) { switch (error) { @@ -651,17 +591,6 @@ } } -void FloatingWorkspaceService::OnTemplateLaunched( - absl::optional<DesksClient::DeskActionError> error, - const base::Uuid& desk_uuid) { - if (error) { - HandleTemplateLaunchErrors(error.value()); - return; - } - RecordLaunchSavedDeskHistogram(DeskTemplateType::kFloatingWorkspace); - RemoveAllPreviousDesksExceptActiveDesk(/*exclude_desk_uuid=*/desk_uuid); -} - void FloatingWorkspaceService::OnTemplateCaptured( absl::optional<DesksClient::DeskActionError> error, std::unique_ptr<DeskTemplate> desk_template) {
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service.h b/chrome/browser/ash/floating_workspace/floating_workspace_service.h index c856f21..6520e58 100644 --- a/chrome/browser/ash/floating_workspace/floating_workspace_service.h +++ b/chrome/browser/ash/floating_workspace/floating_workspace_service.h
@@ -148,8 +148,7 @@ // Launch downloaded floating workspace desk when all conditions are met. // Virtual for testing. virtual void LaunchFloatingWorkspaceTemplate( - const DeskTemplate* desk_template, - bool launch_on_active_desk); + const DeskTemplate* desk_template); // Return the desk client to be used, in test it will return a mocked one. virtual DesksClient* GetDesksClient(); @@ -159,17 +158,9 @@ // If no difference is recorded no upload job will be triggered. bool IsCurrentDeskSameAsPrevious(DeskTemplate* current_desk_template) const; - // Handles the recording of the error for template launch. - void HandleTemplateLaunchErrors(DesksClient::DeskActionError error); - // Handles the recording of the error for template capture. void HandleTemplateCaptureErrors(DesksClient::DeskActionError error); - // Callback function that is run after a floating workspace template - // is downloaded and launched. - void OnTemplateLaunched(absl::optional<DesksClient::DeskActionError> error, - const base::Uuid& desk_uuid); - // Callback function that is run after a floating workspace template is // captured by `desks_storage::DeskSyncBridge`. void OnTemplateCaptured(absl::optional<DesksClient::DeskActionError> error,
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc b/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc index c33f91d..e1815bc 100644 --- a/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc +++ b/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc
@@ -184,7 +184,6 @@ Init(mock_sync_service, fake_desk_sync_service); mock_open_tabs_ = std::make_unique<MockOpenTabsUIDelegate>(); mock_desks_client_ = std::make_unique<MockDesksClient>(); - are_desks_combined_ = false; } void RestoreLocalSessionWindows() override { @@ -218,18 +217,14 @@ DeskTemplate* GetUploadedFloatingWorkspaceTemplate() { return uploaded_desk_template_; } - bool AreDesksCombined() { return are_desks_combined_; } private: sync_sessions::OpenTabsUIDelegate* GetOpenTabsUIDelegate() override { return mock_open_tabs_.get(); } - void LaunchFloatingWorkspaceTemplate(const DeskTemplate* desk_template, - bool launch_on_active_desk) override { - if (launch_on_active_desk) { - are_desks_combined_ = true; - } + void LaunchFloatingWorkspaceTemplate( + const DeskTemplate* desk_template) override { restored_floating_workspace_template_ = desk_template; } void UploadFloatingWorkspaceTemplateToDeskModel( @@ -247,7 +242,6 @@ raw_ptr<DeskTemplate, ExperimentalAsh> uploaded_desk_template_ = nullptr; std::unique_ptr<MockOpenTabsUIDelegate> mock_open_tabs_; std::unique_ptr<MockDesksClient> mock_desks_client_; - bool are_desks_combined_; }; class FloatingWorkspaceServiceTest : public testing::Test { @@ -547,7 +541,6 @@ test_floating_workspace_service_v2.GetRestoredFloatingWorkspaceTemplate() ->template_name(), base::UTF8ToUTF16(template_name)); - EXPECT_FALSE(test_floating_workspace_service_v2.AreDesksCombined()); scoped_feature_list().Reset(); } @@ -612,7 +605,6 @@ test_floating_workspace_service_v2.GetRestoredFloatingWorkspaceTemplate() ->template_name(), base::UTF8ToUTF16(template_name)); - EXPECT_TRUE(test_floating_workspace_service_v2.AreDesksCombined()); scoped_feature_list().Reset(); } @@ -659,7 +651,6 @@ absl::nullopt); EXPECT_FALSE(test_floating_workspace_service_v2 .GetRestoredFloatingWorkspaceTemplate()); - EXPECT_FALSE(test_floating_workspace_service_v2.AreDesksCombined()); scoped_feature_list().Reset(); }
diff --git a/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc b/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc index 18b0835..26eceb3d 100644 --- a/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc +++ b/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc
@@ -351,6 +351,15 @@ out->options_page_url = component_extension.options_page_url; } + const std::string* handwriting_language = + dict.FindString(extensions::manifest_keys::kHandwritingLanguage); + + if (handwriting_language != nullptr) { + out->handwriting_language = *handwriting_language; + } else { + out->handwriting_language = absl::nullopt; + } + return true; }
diff --git a/chrome/browser/ash/nearby/nearby_dependencies_provider_factory.cc b/chrome/browser/ash/nearby/nearby_dependencies_provider_factory.cc index f06cf5c4..8b33331 100644 --- a/chrome/browser/ash/nearby/nearby_dependencies_provider_factory.cc +++ b/chrome/browser/ash/nearby/nearby_dependencies_provider_factory.cc
@@ -7,28 +7,23 @@ #include "ash/constants/ash_features.h" #include "chrome/browser/ash/nearby/nearby_dependencies_provider.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_selections.h" #include "chrome/browser/signin/identity_manager_factory.h" namespace ash::nearby { namespace { +// This needs to be overridden because the default implementation returns +// nullptr for "Ash internal" profiles (i.e. the signin profile), which would +// prevent using this with Quick Start. We allow this service to be created for +// the OTR signin profile for use with Quick Start, and for the regular user +// profile with all other features. See ProfileSelections and +// NearbyProcessManagerFactory documentation for more detail. ProfileSelections BuildNearbyDependenciesProviderProfileSelections() { - // This needs to be overridden because the default implementation returns - // nullptr for OTR profiles, which would prevent using this with Quick Start. - if (features::IsOobeQuickStartEnabled()) { - return ProfileSelections::Builder() - .WithRegular(ProfileSelection::kOwnInstance) - // TODO(crbug.com/1418376): Check if this service is needed in - // Guest mode. - .WithGuest(ProfileSelection::kOwnInstance) - .Build(); - } - return ProfileSelections::Builder() .WithRegular(ProfileSelection::kOriginalOnly) - // TODO(crbug.com/1418376): Check if this service is needed in Guest mode. - .WithGuest(ProfileSelection::kOriginalOnly) + .WithAshInternals(ProfileSelection::kOffTheRecordOnly) .Build(); }
diff --git a/chrome/browser/ash/nearby/nearby_process_manager_factory.cc b/chrome/browser/ash/nearby/nearby_process_manager_factory.cc index 9bdc87b..0b4c360 100644 --- a/chrome/browser/ash/nearby/nearby_process_manager_factory.cc +++ b/chrome/browser/ash/nearby/nearby_process_manager_factory.cc
@@ -30,9 +30,9 @@ // static bool NearbyProcessManagerFactory::CanBeLaunchedForProfile(Profile* profile) { // We allow NearbyProcessManager to be used with the signin profile since it - // is required for OOBE Quick Start. + // is required for OOBE Quick Start. See class documentation for more detail. if (ProfileHelper::IsSigninProfile(profile) && - features::IsOobeQuickStartEnabled()) { + profile->IsPrimaryOTRProfile()) { return true; }
diff --git a/chrome/browser/ash/nearby/nearby_process_manager_factory.h b/chrome/browser/ash/nearby/nearby_process_manager_factory.h index 3dbe3fc1..9dd01136 100644 --- a/chrome/browser/ash/nearby/nearby_process_manager_factory.h +++ b/chrome/browser/ash/nearby/nearby_process_manager_factory.h
@@ -10,20 +10,29 @@ class Profile; -namespace ash { -namespace nearby { +namespace ash::nearby { class NearbyProcessManager; -// Creates a NearbyProcessManager for the primary user. No instance is created -// any other profile. +// Creates a NearbyProcessManager for the primary user as well as for the OTR +// signin profile. +// +// Part of the role of the OTR signin profile is to render the OOBE UI as well +// as the sign-in screen. We need to create a NearbyProcessManager for this +// profile for use with Quick Start. It should be noted that the OTR signin +// profile continues to exist even after the user session begins, which means +// the OTR signin profile and the user profile both exist at the same time. As a +// result there will be multiple instances of the NearbyProcessManager in +// existence at the same time. +// +// TODO(b/280308935): At the end of the Quick Start flow we will release all +// process references which will trigger cleanup in NearbyProcessManager and +// NearbyDependenciesProvider. Although there will be two instances of these +// services in existence at the same time, one will be inactive. class NearbyProcessManagerFactory : public ProfileKeyedServiceFactory { public: static NearbyProcessManager* GetForProfile(Profile* profile); - // Returns true if the nearby process can be launched for |profile| - static bool CanBeLaunchedForProfile(Profile* profile); - static NearbyProcessManagerFactory* GetInstance(); // When true is passed, this factory will create a NearbyProcessManager even @@ -31,13 +40,17 @@ static void SetBypassPrimaryUserCheckForTesting( bool bypass_primary_user_check_for_testing); - private: - friend base::NoDestructor<NearbyProcessManagerFactory>; - - NearbyProcessManagerFactory(); NearbyProcessManagerFactory(const NearbyProcessManagerFactory&) = delete; NearbyProcessManagerFactory& operator=(const NearbyProcessManagerFactory&) = delete; + + private: + friend base::NoDestructor<NearbyProcessManagerFactory>; + + // Returns true if the nearby process can be launched for |profile| + static bool CanBeLaunchedForProfile(Profile* profile); + + NearbyProcessManagerFactory(); ~NearbyProcessManagerFactory() override; // BrowserContextKeyedServiceFactory: @@ -46,7 +59,6 @@ bool ServiceIsCreatedWithBrowserContext() const override; }; -} // namespace nearby -} // namespace ash +} // namespace ash::nearby #endif // CHROME_BROWSER_ASH_NEARBY_NEARBY_PROCESS_MANAGER_FACTORY_H_
diff --git a/chrome/browser/ash/nearby/quick_start_connectivity_service_impl.h b/chrome/browser/ash/nearby/quick_start_connectivity_service_impl.h index 1f78f122..c821c778 100644 --- a/chrome/browser/ash/nearby/quick_start_connectivity_service_impl.h +++ b/chrome/browser/ash/nearby/quick_start_connectivity_service_impl.h
@@ -18,6 +18,8 @@ namespace ash::quick_start { +// TODO(b/280308935): Shut down Nearby Connections when we exit the Quick Start +// flow. class QuickStartConnectivityServiceImpl : public QuickStartConnectivityService { public: explicit QuickStartConnectivityServiceImpl(
diff --git a/chrome/browser/ash/phonehub/phone_hub_manager_factory.cc b/chrome/browser/ash/phonehub/phone_hub_manager_factory.cc index c127cb3..c7e65aa 100644 --- a/chrome/browser/ash/phonehub/phone_hub_manager_factory.cc +++ b/chrome/browser/ash/phonehub/phone_hub_manager_factory.cc
@@ -52,8 +52,9 @@ } bool IsLoggedInAsPrimaryUser(Profile* profile) { - // Guest/incognito profiles cannot use Phone Hub. - if (profile->IsOffTheRecord()) { + // Guest/incognito/signin profiles cannot use Phone Hub. + if (ash::ProfileHelper::IsSigninProfile(profile) || + profile->IsOffTheRecord()) { return false; }
diff --git a/chrome/browser/ash/policy/login/signin_profile_extensions_policy_browsertest.cc b/chrome/browser/ash/policy/login/signin_profile_extensions_policy_browsertest.cc index 9a442d4..db8e7a05 100644 --- a/chrome/browser/ash/policy/login/signin_profile_extensions_policy_browsertest.cc +++ b/chrome/browser/ash/policy/login/signin_profile_extensions_policy_browsertest.cc
@@ -13,19 +13,20 @@ #include "base/memory/raw_ptr.h" #include "base/path_service.h" #include "base/run_loop.h" +#include "base/scoped_observation.h" #include "base/strings/stringprintf.h" #include "base/threading/thread_restrictions.h" #include "base/version.h" #include "chrome/browser/ash/policy/login/signin_profile_extensions_policy_test_base.h" #include "chrome/browser/extensions/crx_installer.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/install_observer.h" +#include "chrome/browser/extensions/install_tracker.h" #include "chrome/browser/policy/extension_force_install_mixin.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_paths.h" #include "components/version_info/version_info.h" #include "content/public/browser/browser_context.h" -#include "content/public/browser/notification_details.h" -#include "content/public/browser/notification_source.h" #include "content/public/test/browser_test.h" #include "content/public/test/test_launcher.h" #include "content/public/test/test_utils.h" @@ -33,7 +34,6 @@ #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/extension_util.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/test_extension_registry_observer.h" #include "extensions/browser/update_observer.h" #include "extensions/common/extension.h" @@ -99,41 +99,37 @@ // Observer that allows waiting for an installation failure of a specific // extension/app. -// TODO(emaxx): Extract this into a more generic helper class for using in other -// tests. -class ExtensionInstallErrorObserver final { +class ExtensionInstallErrorObserver : public extensions::InstallObserver { public: - ExtensionInstallErrorObserver(const Profile* profile, + ExtensionInstallErrorObserver(Profile* profile, const std::string& extension_id) - : profile_(profile), - extension_id_(extension_id), - notification_observer_( - extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR, - base::BindRepeating( - &ExtensionInstallErrorObserver::IsNotificationRelevant, - base::Unretained(this))) {} + : extension_id_(extension_id) { + auto* tracker = extensions::InstallTracker::Get(profile); + CHECK(tracker); + observation_.Observe(tracker); + } ExtensionInstallErrorObserver(const ExtensionInstallErrorObserver&) = delete; ExtensionInstallErrorObserver& operator=( const ExtensionInstallErrorObserver&) = delete; - void Wait() { notification_observer_.Wait(); } + void Wait() { run_loop_.Run(); } - private: - // Callback which is used for |WindowedNotificationObserver| for checking - // whether the condition being awaited is met. - bool IsNotificationRelevant( - const content::NotificationSource& source, - const content::NotificationDetails& details) const { - extensions::CrxInstaller* const crx_installer = - content::Source<extensions::CrxInstaller>(source).ptr(); - return crx_installer->profile() == profile_ && - crx_installer->extension()->id() == extension_id_; + // extensions::InstallObserver: + void OnFinishCrxInstall(content::BrowserContext* context, + const extensions::CrxInstaller& installer, + const std::string& extension_id, + bool success) override { + if (extension_id == extension_id_) { + run_loop_.Quit(); + } } - const raw_ptr<const Profile, ExperimentalAsh> profile_; + private: + base::RunLoop run_loop_; const extensions::ExtensionId extension_id_; - content::WindowedNotificationObserver notification_observer_; + base::ScopedObservation<extensions::InstallTracker, InstallObserver> + observation_{this}; }; // Observer that allows waiting until the specified version of the given
diff --git a/chrome/browser/ash/power/renderer_freezer.cc b/chrome/browser/ash/power/renderer_freezer.cc index f6b8e93..b0c67e6 100644 --- a/chrome/browser/ash/power/renderer_freezer.cc +++ b/chrome/browser/ash/power/renderer_freezer.cc
@@ -20,7 +20,6 @@ #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui.h" #include "extensions/browser/extension_registry.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/process_map.h" #include "extensions/common/extension.h" #include "extensions/common/permissions/api_permission.h"
diff --git a/chrome/browser/ash/power/renderer_freezer_unittest.cc b/chrome/browser/ash/power/renderer_freezer_unittest.cc index 0c1b13b9..15e285418 100644 --- a/chrome/browser/ash/power/renderer_freezer_unittest.cc +++ b/chrome/browser/ash/power/renderer_freezer_unittest.cc
@@ -28,7 +28,6 @@ #include "content/public/browser/site_instance.h" #include "content/public/test/browser_task_environment.h" #include "content/public/test/mock_render_process_host.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/process_manager.h" #include "extensions/browser/process_map.h" #include "extensions/common/extension_builder.h"
diff --git a/chrome/browser/ash/scalable_iph/scalable_iph_browsertest.cc b/chrome/browser/ash/scalable_iph/scalable_iph_browsertest.cc index 00f1e32..b4e3879 100644 --- a/chrome/browser/ash/scalable_iph/scalable_iph_browsertest.cc +++ b/chrome/browser/ash/scalable_iph/scalable_iph_browsertest.cc
@@ -910,8 +910,7 @@ scalable_iph->RecordEvent(scalable_iph::ScalableIph::Event::kUnlocked); } -// TODO(b/290307529): Fix the test. -IN_PROC_BROWSER_TEST_F(ScalableIphBrowserTestBubble, DISABLED_ShowBubble) { +IN_PROC_BROWSER_TEST_F(ScalableIphBrowserTestBubble, ShowBubbleAndDismiss) { EnableTestIphFeature(); mock_delegate()->FakeShowBubble(); @@ -922,13 +921,26 @@ // The action is not performed. EXPECT_CALL(*mock_tracker(), NotifyEvent(kTestButtonActionEvent)).Times(0); - TriggerConditionsCheckWithAFakeEvent( - scalable_iph::ScalableIph::Event::kFiveMinTick); + { + // A timer used for a nudge dismiss is created via + // `AnchoredNudgeManager::Show` call. Call the method in the scoped context + // of `TestMockTimeTaskRunner` as we can fast-forward it below. + base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner()); + TriggerConditionsCheckWithAFakeEvent( + scalable_iph::ScalableIph::Event::kFiveMinTick); + } + + ash::AnchoredNudgeManager* anchored_nudge_manager = + ash::AnchoredNudgeManager::Get(); + CHECK(anchored_nudge_manager); + EXPECT_TRUE(anchored_nudge_manager->IsNudgeShown(kTestBubbleId)); // Default nudge duration is 6 seconds. task_runner()->FastForwardBy(base::Seconds(7)); + + EXPECT_FALSE(anchored_nudge_manager->IsNudgeShown(kTestBubbleId)); + testing::Mock::VerifyAndClearExpectations(mock_tracker()); - // TODO(b/290066999): Verify the nudge is shown. } IN_PROC_BROWSER_TEST_F(ScalableIphBrowserTestBubble, RemoveBubble) {
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_ambient_provider_impl.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_ambient_provider_impl.cc index 639bf0d..6e80f2a 100644 --- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_ambient_provider_impl.cc +++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_ambient_provider_impl.cc
@@ -345,8 +345,6 @@ return; } - // TODO(b/161044021): Add a helper function to get all the albums. Currently - // only load 100 latest modified albums. ash::AmbientBackendController::Get()->FetchSettingsAndAlbums( kBannerWidthPx, kBannerHeightPx, /*num_albums=*/100, base::BindOnce( @@ -642,7 +640,7 @@ void PersonalizationAppAmbientProviderImpl::SyncSettingsAndAlbums() { // Clear the `selected` field, which will be populated with new value below. - // It is neceessary if `UpdateSettings()` failed and we need to reset the + // It is necessary if `UpdateSettings()` failed and we need to reset the // cached settings. for (auto& album : personal_albums_.albums) { album.selected = false; @@ -748,6 +746,8 @@ settings_.reset(); cached_settings_.reset(); settings_sent_for_update_.reset(); + update_settings_retry_backoff_.Reset(); + fetch_settings_retry_backoff_.Reset(); has_pending_fetch_request_ = false; is_updating_backend_ = false; has_pending_updates_for_backend_ = false;
diff --git a/chrome/browser/autofill/credit_card_accessory_controller_impl.cc b/chrome/browser/autofill/credit_card_accessory_controller_impl.cc index 81958198..05e08865 100644 --- a/chrome/browser/autofill/credit_card_accessory_controller_impl.cc +++ b/chrome/browser/autofill/credit_card_accessory_controller_impl.cc
@@ -197,11 +197,8 @@ AccessoryTabType::CREDIT_CARDS, GetTitle(has_suggestions), std::move(info_to_add), std::move(footer_commands)); - if (base::FeatureList::IsEnabled( - features::kAutofillFillMerchantPromoCodeFields)) { - for (auto* offer : GetPromoCodeOffers()) { - data.add_promo_code_info(TranslateOffer(offer)); - } + for (auto* offer : GetPromoCodeOffers()) { + data.add_promo_code_info(TranslateOffer(offer)); } if (has_suggestions && !allow_filling && autofill_manager) {
diff --git a/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc b/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc index 2fd37a19..29585db1 100644 --- a/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc +++ b/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc
@@ -84,10 +84,8 @@ : public ChromeRenderViewHostTestHarness { public: CreditCardAccessoryControllerTest() { - scoped_feature_list_.InitWithFeatures( - /*enabled_features=*/ - {features::kAutofillEnableManualFallbackForVirtualCards}, - /*disabled_features=*/{features::kAutofillFillMerchantPromoCodeFields}); + scoped_feature_list_.InitAndEnableFeature( + features::kAutofillEnableManualFallbackForVirtualCards); } void SetUp() override { @@ -160,12 +158,7 @@ class CreditCardAccessoryControllerTestSupportingPromoCodeOffers : public CreditCardAccessoryControllerTest { public: - CreditCardAccessoryControllerTestSupportingPromoCodeOffers() { - scoped_feature_list_.InitWithFeatures( - /*enabled_features=*/ - {features::kAutofillFillMerchantPromoCodeFields}, - /*disabled_features=*/{}); - } + CreditCardAccessoryControllerTestSupportingPromoCodeOffers() = default; private: base::test::ScopedFeatureList scoped_feature_list_; @@ -528,8 +521,7 @@ EXPECT_EQ(result.user_info_list()[1].icon_url(), GURL()); } -// Tests that when |kAutofillFillMerchantPromoCodeFields| feature is enabled, -// promo codes are shown. +// Tests that promo codes are shown. TEST_F(CreditCardAccessoryControllerTestSupportingPromoCodeOffers, RefreshSuggestionsWithPromoCodeOffers) { CreditCard card = test::GetCreditCard(); @@ -578,39 +570,4 @@ .Build()); } -// Tests that when |kAutofillFillMerchantPromoCodeFields| feature is disabled, -// promo codes are not shown. -TEST_F(CreditCardAccessoryControllerTest, - RefreshSuggestionsWithPromoCodeOffers) { - CreditCard card = test::GetCreditCard(); - data_manager_.AddCreditCard(card); - AutofillOfferData promo_code = test::GetPromoCodeOfferData( - /*merchant_origin=*/GURL(kExampleSite)); - data_manager_.AddAutofillOfferData(promo_code); - AccessorySheetData result(autofill::AccessoryTabType::CREDIT_CARDS, - std::u16string()); - - EXPECT_CALL(mock_mf_controller_, RefreshSuggestions(_)) - .WillOnce(SaveArg<0>(&result)); - ASSERT_TRUE(controller()); - controller()->RefreshSuggestions(); - - EXPECT_EQ(result, controller()->GetSheetData()); - // Promo code offers are available, but not shown. - EXPECT_EQ(result, - CreditCardAccessorySheetDataBuilder() - .AddUserInfo(kVisaCard) - .AppendField(card.ObfuscatedNumberWithVisibleLastFourDigits(), - /*text_to_fill=*/std::u16string(), - card.ObfuscatedNumberWithVisibleLastFourDigits(), - card.guid(), - /*is_obfuscated=*/false, - /*selectable=*/true) - .AppendSimpleField(card.Expiration2DigitMonthAsString()) - .AppendSimpleField(card.Expiration4DigitYearAsString()) - .AppendSimpleField(card.GetRawInfo(CREDIT_CARD_NAME_FULL)) - .AppendSimpleField(std::u16string()) - .Build()); -} - } // namespace autofill
diff --git a/chrome/browser/back_press/android/BUILD.gn b/chrome/browser/back_press/android/BUILD.gn index 639a2bc0..3364e9e 100644 --- a/chrome/browser/back_press/android/BUILD.gn +++ b/chrome/browser/back_press/android/BUILD.gn
@@ -42,6 +42,7 @@ "//chrome/browser/flags:java", "//components/browser_ui/widget/android:java", "//third_party/androidx:androidx_activity_activity_java", + "//third_party/androidx:androidx_annotation_annotation_java", "//third_party/androidx:androidx_lifecycle_lifecycle_common_java", "//third_party/androidx:androidx_lifecycle_lifecycle_runtime_java", "//third_party/junit:junit",
diff --git a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java index 6d987ac..876f24a 100644 --- a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java +++ b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java
@@ -7,6 +7,7 @@ import android.text.format.DateUtils; import android.util.SparseIntArray; +import androidx.activity.BackEventCompat; import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -69,9 +70,33 @@ } private final OnBackPressedCallback mCallback = new OnBackPressedCallback(false) { + private BackPressHandler mActiveHandler; + @Override public void handleOnBackPressed() { BackPressManager.this.handleBackPress(); + mActiveHandler = null; + } + + // Following methods are only triggered on API 34+. + @Override + public void handleOnBackStarted(@NonNull BackEventCompat backEvent) { + mActiveHandler = getEnabledBackPressHandler(); + assert mActiveHandler != null; + mActiveHandler.handleOnBackStarted(backEvent); + } + + @Override + public void handleOnBackCancelled() { + assert mActiveHandler != null; + mActiveHandler.handleOnBackCancelled(); + mActiveHandler = null; + } + + @Override + public void handleOnBackProgressed(@NonNull BackEventCompat backEvent) { + assert mActiveHandler != null; + mActiveHandler.handleOnBackProgressed(backEvent); } }; @@ -242,6 +267,20 @@ } } + @VisibleForTesting + BackPressHandler getEnabledBackPressHandler() { + for (int i = 0; i < mHandlers.length; i++) { + BackPressHandler handler = mHandlers[i]; + if (handler == null) continue; + Boolean enabled = handler.getHandleBackPressChangedSupplier().get(); + if (enabled != null && enabled) { + return handler; + } + } + assert false; + return null; + } + private void handleBackPress() { var failed = new ArrayList<String>(); for (int i = 0; i < mHandlers.length; i++) {
diff --git a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerUnitTest.java b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerUnitTest.java index 9b29f36b..683f55df 100644 --- a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerUnitTest.java +++ b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerUnitTest.java
@@ -6,10 +6,14 @@ import android.os.Build; +import androidx.activity.BackEventCompat; +import androidx.annotation.NonNull; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.robolectric.annotation.Config; import org.chromium.base.supplier.ObservableSupplierImpl; @@ -41,6 +45,15 @@ public CallbackHelper getCallbackHelper() { return mCallbackHelper; } + + @Override + public void handleOnBackCancelled() {} + + @Override + public void handleOnBackProgressed(@NonNull BackEventCompat backEvent) {} + + @Override + public void handleOnBackStarted(@NonNull BackEventCompat backEvent) {} } @Test @@ -66,6 +79,8 @@ h1.getCallbackHelper().getCallCount()); h1.getHandleBackPressChangedSupplier().set(true); + Assert.assertEquals( + "Should return the active handler", h1, manager.getEnabledBackPressHandler()); Assert.assertTrue("Callback should be enabled if any of handlers are enabled", manager.getCallback().isEnabled()); manager.getCallback().handleOnBackPressed(); @@ -98,6 +113,8 @@ manager.getCallback().isEnabled()); h2.getHandleBackPressChangedSupplier().set(true); + Assert.assertEquals( + "Should return the first active handler", h2, manager.getEnabledBackPressHandler()); Assert.assertTrue("Callback should be enabled if any of handlers are enabled.", manager.getCallback().isEnabled()); @@ -127,6 +144,8 @@ h1.getHandleBackPressChangedSupplier().set(true); h2.getHandleBackPressChangedSupplier().set(true); manager.getCallback().handleOnBackPressed(); + Assert.assertEquals("Should return the active handler of higher priority", h1, + manager.getEnabledBackPressHandler()); Assert.assertEquals("Enabled handler of higher priority should intercept the back gesture", 1, h1.getCallbackHelper().getCallCount()); @@ -303,6 +322,70 @@ manager.getCallback().isEnabled()); } + @Test + public void testOnBackPressProgressed() { + BackPressManager manager = new BackPressManager(); + EmptyBackPressHandler h1 = Mockito.spy(new EmptyBackPressHandler()); + EmptyBackPressHandler h2 = Mockito.spy(new EmptyBackPressHandler()); + manager.addHandler(h1, 0); + manager.addHandler(h2, 1); + h1.getHandleBackPressChangedSupplier().set(false); + h2.getHandleBackPressChangedSupplier().set(true); + Assert.assertEquals( + "Should return the active handler", h2, manager.getEnabledBackPressHandler()); + var backEvent = new BackEventCompat(0, 0, 0, BackEventCompat.EDGE_LEFT); + manager.getCallback().handleOnBackStarted(backEvent); + Mockito.verify(h2).handleOnBackStarted(backEvent); + + backEvent = new BackEventCompat(1, 0, .5f, BackEventCompat.EDGE_LEFT); + manager.getCallback().handleOnBackProgressed(backEvent); + Mockito.verify(h2).handleOnBackProgressed(backEvent); + + backEvent = new BackEventCompat(2, 0, 1, BackEventCompat.EDGE_LEFT); + manager.getCallback().handleOnBackProgressed(backEvent); + Mockito.verify(h2).handleOnBackProgressed(backEvent); + + manager.getCallback().handleOnBackPressed(); + Mockito.verify(h2).handleBackPress(); + + Mockito.verify(h2, + Mockito.never().description( + "Cancelled should never be called if back is not cancelled")) + .handleOnBackCancelled(); + } + + @Test + public void testOnBackPressCancelled() { + BackPressManager manager = new BackPressManager(); + EmptyBackPressHandler h1 = Mockito.spy(new EmptyBackPressHandler()); + EmptyBackPressHandler h2 = Mockito.spy(new EmptyBackPressHandler()); + manager.addHandler(h1, 0); + manager.addHandler(h2, 1); + h1.getHandleBackPressChangedSupplier().set(false); + h2.getHandleBackPressChangedSupplier().set(true); + Assert.assertEquals( + "Should return the active handler", h2, manager.getEnabledBackPressHandler()); + var backEvent = new BackEventCompat(0, 0, 0, BackEventCompat.EDGE_LEFT); + manager.getCallback().handleOnBackStarted(backEvent); + Mockito.verify(h2).handleOnBackStarted(backEvent); + + backEvent = new BackEventCompat(1, 0, .5f, BackEventCompat.EDGE_LEFT); + manager.getCallback().handleOnBackProgressed(backEvent); + Mockito.verify(h2).handleOnBackProgressed(backEvent); + + backEvent = new BackEventCompat(2, 0, 1, BackEventCompat.EDGE_LEFT); + manager.getCallback().handleOnBackProgressed(backEvent); + Mockito.verify(h2).handleOnBackProgressed(backEvent); + + manager.getCallback().handleOnBackCancelled(); + Mockito.verify(h2).handleOnBackCancelled(); + + Mockito.verify(h2, + Mockito.never().description( + "handleBackPress should never be called if back is cancelled")) + .handleBackPress(); + } + private int getHandlerCount(BackPressManager manager) { int count = 0; for (BackPressHandler handler : manager.getHandlersForTesting()) {
diff --git a/chrome/browser/certificate_provider/test_certificate_provider_extension.cc b/chrome/browser/certificate_provider/test_certificate_provider_extension.cc index de9b90f..127fe0c 100644 --- a/chrome/browser/certificate_provider/test_certificate_provider_extension.cc +++ b/chrome/browser/certificate_provider/test_certificate_provider_extension.cc
@@ -29,7 +29,6 @@ #include "crypto/rsa_private_key.h" #include "extensions/browser/api/test/test_api.h" #include "extensions/browser/event_router.h" -#include "extensions/browser/notification_types.h" #include "extensions/common/api/test.h" #include "net/cert/asn1_util.h" #include "net/cert/x509_certificate.h"
diff --git a/chrome/browser/chrome_browser_main_extra_parts_nacl_deprecation.cc b/chrome/browser/chrome_browser_main_extra_parts_nacl_deprecation.cc index ca7479f..52c30b93 100644 --- a/chrome/browser/chrome_browser_main_extra_parts_nacl_deprecation.cc +++ b/chrome/browser/chrome_browser_main_extra_parts_nacl_deprecation.cc
@@ -11,6 +11,7 @@ #include "chrome/common/chrome_switches.h" #include "chrome/common/ppapi_utils.h" #include "chrome/common/pref_names.h" +#include "components/nacl/common/buildflags.h" #include "components/prefs/pref_service.h" namespace { @@ -36,8 +37,9 @@ ); void ChromeBrowserMainExtraPartsNaclDeprecation::PostEarlyInitialization() { +#if BUILDFLAG(ENABLE_NACL) if (!ShouldNaClBeAllowed()) { - base::CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kDisableNaCl); + DisallowNacl(); } +#endif }
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 4089e87..5152b32 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc
@@ -2905,6 +2905,10 @@ } #endif // BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(ENABLE_NACL) + AppendDisableNaclSwitchIfNecessary(command_line); +#endif + // Please keep this in alphabetical order. static const char* const kSwitchNames[] = { autofill::switches::kIgnoreAutocompleteOffForAutofill, @@ -2924,7 +2928,6 @@ switches::kAllowInsecureLocalhost, switches::kAppsGalleryURL, switches::kDisableJavaScriptHarmonyShipping, - switches::kDisableNaCl, variations::switches::kEnableBenchmarking, switches::kEnableDistillabilityService, switches::kEnableNaCl, @@ -2982,6 +2985,11 @@ #endif MaybeAppendSecureOriginsAllowlistSwitch(command_line); } else if (process_type == switches::kZygoteProcess) { + // It would be preferable to call AppendDisableNaclSwitchIfNecessary to + // disable NaCl for the zygote process. Unfortunately that method depends on + // state (including policy) that is determined after the zygote is forked. + // Instead we rely on renderers overriding the zygote state. + // Load (in-process) Pepper plugins in-process in the zygote pre-sandbox. #if BUILDFLAG(ENABLE_NACL) static const char* const kSwitchNames[] = {
diff --git a/chrome/browser/chrome_notification_types.h b/chrome/browser/chrome_notification_types.h index 94b4b33..3b6ebdb5 100644 --- a/chrome/browser/chrome_notification_types.h +++ b/chrome/browser/chrome_notification_types.h
@@ -7,19 +7,8 @@ #include "build/build_config.h" #include "build/chromeos_buildflags.h" -#include "extensions/buildflags/buildflags.h" - -#if BUILDFLAG(ENABLE_EXTENSIONS) -#include "extensions/browser/notification_types.h" -#else #include "content/public/browser/notification_types.h" -#endif - -#if BUILDFLAG(ENABLE_EXTENSIONS) -#define PREVIOUS_END extensions::NOTIFICATION_EXTENSIONS_END -#else -#define PREVIOUS_END content::NOTIFICATION_CONTENT_END -#endif +#include "extensions/buildflags/buildflags.h" // ** // ** NOTICE @@ -35,7 +24,7 @@ namespace chrome { enum NotificationType { - NOTIFICATION_CHROME_START = PREVIOUS_END, + NOTIFICATION_CHROME_START = content::NOTIFICATION_CONTENT_END, // Authentication ----------------------------------------------------------
diff --git a/chrome/browser/component_updater/chrome_client_side_phishing_component_installer.cc b/chrome/browser/component_updater/chrome_client_side_phishing_component_installer.cc deleted file mode 100644 index 8903c56..0000000 --- a/chrome/browser/component_updater/chrome_client_side_phishing_component_installer.cc +++ /dev/null
@@ -1,75 +0,0 @@ -// Copyright 2021 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/component_updater/chrome_client_side_phishing_component_installer.h" - -#include <memory> -#include <utility> - -#include "base/files/file.h" -#include "base/files/file_path.h" -#include "base/files/file_util.h" -#include "base/functional/bind.h" -#include "base/functional/callback_forward.h" -#include "base/location.h" -#include "base/memory/ref_counted.h" -#include "base/task/task_traits.h" -#include "base/task/thread_pool.h" -#include "components/component_updater/component_installer.h" -#include "components/component_updater/installer_policies/client_side_phishing_component_installer_policy.h" -#include "components/safe_browsing/content/browser/client_side_phishing_model.h" -#include "components/safe_browsing/core/common/features.h" -#include "components/update_client/update_client.h" - -using component_updater::ComponentUpdateService; - -namespace component_updater { -namespace { - -void LoadFromDisk(const base::FilePath& pb_path, - const base::FilePath& visual_tflite_model_path) { - if (pb_path.empty()) - return; - - std::string binary_pb; - if (!base::ReadFileToString(pb_path, &binary_pb)) - binary_pb.clear(); - - base::File visual_tflite_model(visual_tflite_model_path, - base::File::FLAG_OPEN | base::File::FLAG_READ); - - // The ClientSidePhishingModel singleton will react appropriately if the - // |binary_pb| is empty or |visual_tflite_model| is invalid. - safe_browsing::ClientSidePhishingModel::GetInstance() - ->PopulateFromDynamicUpdate(binary_pb, std::move(visual_tflite_model)); -} - -void PopulateModelFromFiles(const base::FilePath& install_dir) { - base::ThreadPool::PostTask( - FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, - base::BindOnce(&LoadFromDisk, - install_dir.Append(kClientModelBinaryPbFileName), - install_dir.Append(kVisualTfLiteModelFileName))); -} - -update_client::InstallerAttributes GetInstallerAttributes() { - update_client::InstallerAttributes attributes; - - // Pass the tag parameter to the installer as the "tag" attribute; it will - // be used to choose which binary is downloaded. - attributes["tag"] = safe_browsing::GetClientSideDetectionTag(); - return attributes; -} - -} // namespace - -void RegisterClientSidePhishingComponent(ComponentUpdateService* cus) { - auto installer = base::MakeRefCounted<ComponentInstaller>( - std::make_unique<ClientSidePhishingComponentInstallerPolicy>( - base::BindRepeating(&PopulateModelFromFiles), - base::BindRepeating(&GetInstallerAttributes))); - installer->Register(cus, base::OnceClosure()); -} - -} // namespace component_updater
diff --git a/chrome/browser/component_updater/chrome_client_side_phishing_component_installer.h b/chrome/browser/component_updater/chrome_client_side_phishing_component_installer.h deleted file mode 100644 index 8dd5a89..0000000 --- a/chrome/browser/component_updater/chrome_client_side_phishing_component_installer.h +++ /dev/null
@@ -1,18 +0,0 @@ -// Copyright 2021 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_COMPONENT_UPDATER_CHROME_CLIENT_SIDE_PHISHING_COMPONENT_INSTALLER_H_ -#define CHROME_BROWSER_COMPONENT_UPDATER_CHROME_CLIENT_SIDE_PHISHING_COMPONENT_INSTALLER_H_ - -namespace component_updater { - -class ComponentUpdateService; - -// Call once during startup to make the component update service aware of -// the Client Side Phishing component. -void RegisterClientSidePhishingComponent(ComponentUpdateService* cus); - -} // namespace component_updater - -#endif // CHROME_BROWSER_COMPONENT_UPDATER_CHROME_CLIENT_SIDE_PHISHING_COMPONENT_INSTALLER_H_
diff --git a/chrome/browser/component_updater/registration.cc b/chrome/browser/component_updater/registration.cc index 00f049e..3aa0109 100644 --- a/chrome/browser/component_updater/registration.cc +++ b/chrome/browser/component_updater/registration.cc
@@ -18,7 +18,6 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/buildflags.h" #include "chrome/browser/component_updater/app_provisioning_component_installer.h" -#include "chrome/browser/component_updater/chrome_client_side_phishing_component_installer.h" #include "chrome/browser/component_updater/chrome_origin_trials_component_installer.h" #include "chrome/browser/component_updater/commerce_heuristics_component_installer.h" #include "chrome/browser/component_updater/crl_set_component_installer.h" @@ -211,13 +210,6 @@ RegisterAutofillStatesComponent(cus, g_browser_process->local_state()); - // OptimizationGuide provides the model through their services, so if the - // flag is false, a registration to CSD-Phishing component is needed - if (!base::FeatureList::IsEnabled( - safe_browsing::kClientSideDetectionModelOptimizationGuide)) { - RegisterClientSidePhishingComponent(cus); - } - #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE) && !BUILDFLAG(IS_CHROMEOS) ManageScreenAIComponentRegistration(cus, g_browser_process->local_state()); #endif // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE) && !BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/content_settings/content_settings_browsertest.cc b/chrome/browser/content_settings/content_settings_browsertest.cc index 0abd487..7583f0a 100644 --- a/chrome/browser/content_settings/content_settings_browsertest.cc +++ b/chrome/browser/content_settings/content_settings_browsertest.cc
@@ -75,7 +75,7 @@ #include "third_party/widevine/cdm/buildflags.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif #if BUILDFLAG(ENABLE_PLUGINS)
diff --git a/chrome/browser/devtools/protocol/devtools_protocol_browsertest.cc b/chrome/browser/devtools/protocol/devtools_protocol_browsertest.cc index 1a09548..3ffff6e 100644 --- a/chrome/browser/devtools/protocol/devtools_protocol_browsertest.cc +++ b/chrome/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -21,7 +21,6 @@ #include "chrome/browser/data_saver/data_saver.h" #include "chrome/browser/devtools/devtools_window.h" #include "chrome/browser/devtools/protocol/devtools_protocol_test_support.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_service.h" #include "chrome/browser/dips/dips_storage.h" #include "chrome/browser/extensions/extension_service.h" @@ -397,7 +396,7 @@ protected: void SetUp() override { scoped_feature_list_.InitAndEnableFeatureWithParameters( - dips::kFeature, + features::kDIPS, {{"delete", "true"}, {"triggering_action", "stateful_bounce"}}); DevToolsProtocolTest::SetUp(); @@ -454,13 +453,13 @@ : public DevToolsProtocolTest, public testing::WithParamInterface<std::tuple<bool, bool, std::string>> { // The fields of `GetParam()` indicate/control the following: - // `std::get<0>(GetParam())` => `dips::kFeature` - // `std::get<1>(GetParam())` => `dips::kDeletionEnabled` - // `std::get<2>(GetParam())` => `dips::kTriggeringAction` + // `std::get<0>(GetParam())` => `features::kDIPS` + // `std::get<1>(GetParam())` => `features::kDIPSDeletionEnabled` + // `std::get<2>(GetParam())` => `features::kDIPSTriggeringAction` // - // In order for Bounce Tracking Mitigations to take effect, `kFeature` must - // be true/enabled, `kDeletionEnabled` must be true, and `kTriggeringAction` - // must NOT be `none`. + // In order for Bounce Tracking Mitigations to take effect, `features::kDIPS` + // must be true/enabled, `kDeletionEnabled` must be true, and + // `kTriggeringAction` must NOT be `none`. // // Note: Bounce Tracking Mitigations issues only report sites that would // be affected when `kTriggeringAction` is set to 'stateful_bounce'. @@ -469,11 +468,11 @@ void SetUp() override { if (std::get<0>(GetParam())) { scoped_feature_list_.InitAndEnableFeatureWithParameters( - dips::kFeature, + features::kDIPS, {{"delete", (std::get<1>(GetParam()) ? "true" : "false")}, {"triggering_action", std::get<2>(GetParam())}}); } else { - scoped_feature_list_.InitAndDisableFeature(dips::kFeature); + scoped_feature_list_.InitAndDisableFeature(features::kDIPS); } DevToolsProtocolTest::SetUp();
diff --git a/chrome/browser/devtools/protocol/system_info_handler.cc b/chrome/browser/devtools/protocol/system_info_handler.cc index 8788073..bcbb1449 100644 --- a/chrome/browser/devtools/protocol/system_info_handler.cc +++ b/chrome/browser/devtools/protocol/system_info_handler.cc
@@ -4,10 +4,10 @@ #include "chrome/browser/devtools/protocol/system_info_handler.h" -#include "chrome/browser/dips/dips_features.h" -#include "chrome/browser/dips/dips_utils.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" +#include "content/public/common/content_features.h" +#include "content/public/common/dips_utils.h" SystemInfoHandler::SystemInfoHandler(protocol::UberDispatcher* dispatcher) { protocol::SystemInfo::Dispatcher::wire(dispatcher, this); @@ -19,10 +19,10 @@ const std::string& in_featureState, bool* featureEnabled) { if (in_featureState == "DIPS") { - *featureEnabled = - base::FeatureList::IsEnabled(dips::kFeature) && - dips::kDeletionEnabled.Get() && - (dips::kTriggeringAction.Get() != DIPSTriggeringAction::kNone); + *featureEnabled = base::FeatureList::IsEnabled(features::kDIPS) && + features::kDIPSDeletionEnabled.Get() && + (features::kDIPSTriggeringAction.Get() != + content::DIPSTriggeringAction::kNone); return protocol::Response::Success(); }
diff --git a/chrome/browser/dips/dips_bounce_detector.cc b/chrome/browser/dips/dips_bounce_detector.cc index 44f34d0..9b54024e 100644 --- a/chrome/browser/dips/dips_bounce_detector.cc +++ b/chrome/browser/dips/dips_bounce_detector.cc
@@ -25,7 +25,6 @@ #include "chrome/browser/3pcd/heuristics/opener_heuristic_tab_helper.h" #include "chrome/browser/3pcd/heuristics/opener_heuristic_utils.h" #include "chrome/browser/dips/cookie_access_filter.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_redirect_info.h" #include "chrome/browser/dips/dips_service.h" #include "chrome/browser/dips/dips_storage.h" @@ -38,6 +37,7 @@ #include "content/public/browser/page.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents_observer.h" +#include "content/public/common/content_features.h" #include "net/cookies/canonical_cookie.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" @@ -126,7 +126,7 @@ /*redirect_prefix_count=*/0u), client_bounce_detection_timer_( FROM_HERE, - dips::kClientBounceDetectionTimeout.Get(), + features::kDIPSClientBounceDetectionTimeout.Get(), base::BindRepeating( &DIPSBounceDetector::OnClientBounceDetectionTimeout, base::Unretained(this)),
diff --git a/chrome/browser/dips/dips_bounce_detector.h b/chrome/browser/dips/dips_bounce_detector.h index 31681e4..01bf287 100644 --- a/chrome/browser/dips/dips_bounce_detector.h +++ b/chrome/browser/dips/dips_bounce_detector.h
@@ -15,7 +15,6 @@ #include "base/timer/timer.h" #include "base/types/optional_ref.h" #include "chrome/browser/dips/cookie_access_filter.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_redirect_info.h" #include "chrome/browser/dips/dips_service.h" #include "chrome/browser/dips/dips_utils.h"
diff --git a/chrome/browser/dips/dips_bounce_detector_unittest.cc b/chrome/browser/dips/dips_bounce_detector_unittest.cc index 79d57bd0..756fafa8 100644 --- a/chrome/browser/dips/dips_bounce_detector_unittest.cc +++ b/chrome/browser/dips/dips_bounce_detector_unittest.cc
@@ -16,11 +16,11 @@ #include "base/test/task_environment.h" #include "base/time/time.h" #include "base/types/pass_key.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_service.h" #include "chrome/browser/dips/dips_test_utils.h" #include "chrome/browser/dips/dips_utils.h" #include "components/ukm/test_ukm_recorder.h" +#include "content/public/common/content_features.h" #include "services/metrics/public/cpp/ukm_source_id.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -273,10 +273,10 @@ task_environment_.RunUntilIdle(); } - // Advances the mocked clock by `dips::kClientBounceDetectionTimeout` to - // trigger the closure of the pending redirect chain. + // Advances the mocked clock by `features::kDIPSClientBounceDetectionTimeout` + // to trigger the closure of the pending redirect chain. void EndPendingRedirectChain() { - AdvanceDIPSTime(dips::kClientBounceDetectionTimeout.Get()); + AdvanceDIPSTime(features::kDIPSClientBounceDetectionTimeout.Get()); } const std::string& URLForNavigationSourceId(ukm::SourceId source_id) { @@ -340,13 +340,15 @@ .RedirectTo("http://c.test") .RedirectTo("http://d.test") .Finish(true); - AdvanceDIPSTime(dips::kClientBounceDetectionTimeout.Get() - base::Seconds(1)); + AdvanceDIPSTime(features::kDIPSClientBounceDetectionTimeout.Get() - + base::Seconds(1)); auto mocked_bounce_time_2 = GetCurrentTime(); StartNavigation("http://e.test", kNoUserGesture) .RedirectTo("http://f.test") .RedirectTo("http://g.test") .Finish(true); - AdvanceDIPSTime(dips::kClientBounceDetectionTimeout.Get() - base::Seconds(1)); + AdvanceDIPSTime(features::kDIPSClientBounceDetectionTimeout.Get() - + base::Seconds(1)); auto mocked_bounce_time_3 = GetCurrentTime(); StartNavigation("http://h.test", kWithUserGesture) .RedirectTo("http://i.test") @@ -389,13 +391,13 @@ TEST_F(DIPSBounceDetectorTest, DetectStatefulRedirects_After_ClientBounceDetectionTimeout) { NavigateTo("http://a.test", kWithUserGesture); - AdvanceDIPSTime(dips::kClientBounceDetectionTimeout.Get()); + AdvanceDIPSTime(features::kDIPSClientBounceDetectionTimeout.Get()); auto mocked_bounce_time_1 = GetCurrentTime(); StartNavigation("http://b.test", kWithUserGesture) .RedirectTo("http://c.test") .RedirectTo("http://d.test") .Finish(true); - AdvanceDIPSTime(dips::kClientBounceDetectionTimeout.Get()); + AdvanceDIPSTime(features::kDIPSClientBounceDetectionTimeout.Get()); auto mocked_bounce_time_2 = GetCurrentTime(); StartNavigation("http://e.test", kNoUserGesture) .RedirectTo("http://f.test") @@ -526,7 +528,8 @@ TEST_F(DIPSBounceDetectorTest, DetectStatefulRedirect_Client) { NavigateTo("http://a.test", kWithUserGesture); NavigateTo("http://b.test", kWithUserGesture); - AdvanceDIPSTime(dips::kClientBounceDetectionTimeout.Get() - base::Seconds(1)); + AdvanceDIPSTime(features::kDIPSClientBounceDetectionTimeout.Get() - + base::Seconds(1)); NavigateTo("http://c.test", kNoUserGesture); auto mocked_bounce_time = GetCurrentTime(); @@ -545,7 +548,8 @@ NavigateTo("http://a.test", kWithUserGesture); AccessClientCookie(CookieOperation::kRead); AccessClientCookie(CookieOperation::kChange); - AdvanceDIPSTime(dips::kClientBounceDetectionTimeout.Get() - base::Seconds(1)); + AdvanceDIPSTime(features::kDIPSClientBounceDetectionTimeout.Get() - + base::Seconds(1)); NavigateTo("http://b.test", kNoUserGesture); auto mocked_bounce_time = GetCurrentTime();
diff --git a/chrome/browser/dips/dips_browser_signin_detector_unittest.cc b/chrome/browser/dips/dips_browser_signin_detector_unittest.cc index c9b8af2..63bb747 100644 --- a/chrome/browser/dips/dips_browser_signin_detector_unittest.cc +++ b/chrome/browser/dips/dips_browser_signin_detector_unittest.cc
@@ -17,7 +17,6 @@ #include "base/test/mock_callback.h" #include "base/test/scoped_feature_list.h" #include "base/test/test_file_util.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_service.h" #include "chrome/browser/dips/dips_test_utils.h" #include "chrome/browser/dips/dips_utils.h" @@ -31,6 +30,7 @@ #include "components/signin/public/identity_manager/account_managed_status_finder.h" #include "components/signin/public/identity_manager/identity_test_environment.h" #include "components/signin/public/identity_manager/identity_test_utils.h" +#include "content/public/common/content_features.h" #include "content/public/test/browser_task_environment.h" #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" #include "services/network/test/test_url_loader_factory.h" @@ -125,7 +125,7 @@ base::StrCat({"foo@", kIdentityProviderDomain}), kIdentityProviderDomain}; private: - ScopedInitFeature feature_{dips::kFeature, + ScopedInitFeature feature_{features::kDIPS, /*enable:*/ true, /*params:*/ {{"persist_database", "true"}}}; network::TestURLLoaderFactory test_url_loader_factory_;
diff --git a/chrome/browser/dips/dips_cleanup_service.cc b/chrome/browser/dips/dips_cleanup_service.cc index 30c0066..317cec6 100644 --- a/chrome/browser/dips/dips_cleanup_service.cc +++ b/chrome/browser/dips/dips_cleanup_service.cc
@@ -5,11 +5,11 @@ #include "chrome/browser/dips/dips_cleanup_service.h" #include "chrome/browser/dips/dips_cleanup_service_factory.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_storage.h" +#include "content/public/common/content_features.h" DIPSCleanupService::DIPSCleanupService(content::BrowserContext* context) { - DCHECK(!base::FeatureList::IsEnabled(dips::kFeature)); + DCHECK(!base::FeatureList::IsEnabled(features::kDIPS)); DIPSStorage::DeleteDatabaseFiles( GetDIPSFilePath(context), base::BindOnce(&DIPSCleanupService::OnCleanupFinished,
diff --git a/chrome/browser/dips/dips_cleanup_service_factory.cc b/chrome/browser/dips/dips_cleanup_service_factory.cc index 49c66cd5..599f6a91 100644 --- a/chrome/browser/dips/dips_cleanup_service_factory.cc +++ b/chrome/browser/dips/dips_cleanup_service_factory.cc
@@ -6,7 +6,7 @@ #include "base/no_destructor.h" #include "chrome/browser/dips/dips_cleanup_service.h" -#include "chrome/browser/dips/dips_features.h" +#include "content/public/common/content_features.h" // static DIPSCleanupService* DIPSCleanupServiceFactory::GetForBrowserContext( @@ -22,7 +22,7 @@ /*static*/ ProfileSelections DIPSCleanupServiceFactory::CreateProfileSelections() { - if (!base::FeatureList::IsEnabled(dips::kFeature)) { + if (!base::FeatureList::IsEnabled(features::kDIPS)) { return ProfileSelections::Builder() .WithRegular(ProfileSelection::kOriginalOnly) .WithGuest(ProfileSelection::kNone)
diff --git a/chrome/browser/dips/dips_database.cc b/chrome/browser/dips/dips_database.cc index 22e3058..da84273 100644 --- a/chrome/browser/dips/dips_database.cc +++ b/chrome/browser/dips/dips_database.cc
@@ -18,6 +18,7 @@ #include "base/threading/thread_restrictions.h" #include "base/time/time.h" #include "chrome/browser/dips/dips_utils.h" +#include "content/public/common/content_features.h" #include "sql/database.h" #include "sql/error_delegate_util.h" #include "sql/init_status.h" @@ -100,7 +101,7 @@ sql::DatabaseOptions{.exclusive_locking = true, .page_size = 4096, .cache_size = 32})) { - DCHECK(base::FeatureList::IsEnabled(dips::kFeature)); + DCHECK(base::FeatureList::IsEnabled(features::kDIPS)); base::AssertLongCPUWorkAllowed(); if (db_path.has_value()) { DCHECK(!db_path->empty()) @@ -887,7 +888,8 @@ DCHECK(db_->IsSQLValid(kClearAllExpiredBouncesTableSql)); sql::Statement bounces_statement( db_->GetCachedStatement(SQL_FROM_HERE, kClearAllExpiredBouncesTableSql)); - bounces_statement.BindTime(0, clock_->Now() - dips::kInteractionTtl.Get()); + bounces_statement.BindTime( + 0, clock_->Now() - features::kDIPSInteractionTtl.Get()); if (!bounces_statement.Run()) { return 0; }
diff --git a/chrome/browser/dips/dips_database.h b/chrome/browser/dips/dips_database.h index 8876466..bf8eeea 100644 --- a/chrome/browser/dips/dips_database.h +++ b/chrome/browser/dips/dips_database.h
@@ -12,8 +12,8 @@ #include "base/time/clock.h" #include "base/time/default_clock.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_utils.h" +#include "content/public/common/content_features.h" #include "sql/database.h" #include "sql/init_status.h" #include "sql/meta_table.h" @@ -74,7 +74,7 @@ // This is implicitly `inline`. Don't move its definition to the .cc file. bool HasExpired(absl::optional<base::Time> time) { return time.has_value() && - (time.value() + dips::kInteractionTtl.Get()) < clock_->Now(); + (time.value() + features::kDIPSInteractionTtl.Get()) < clock_->Now(); } absl::optional<StateValue> Read(const std::string& site);
diff --git a/chrome/browser/dips/dips_database_unittest.cc b/chrome/browser/dips/dips_database_unittest.cc index 8edf21b..51fdaac 100644 --- a/chrome/browser/dips/dips_database_unittest.cc +++ b/chrome/browser/dips/dips_database_unittest.cc
@@ -21,9 +21,10 @@ #include "base/test/scoped_feature_list.h" #include "base/test/simple_test_clock.h" #include "base/time/time.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_test_utils.h" #include "chrome/browser/dips/dips_utils.h" +#include "content/public/common/content_features.h" +#include "content/public/common/dips_utils.h" #include "sql/database.h" #include "sql/sqlite_result_code_values.h" #include "sql/test/scoped_error_expecter.h" @@ -118,7 +119,7 @@ DIPSDatabaseTest::SetUp(); // Use inf ttl to prevent interactions (including web authn assertions) from // expiring unintentionally. - features_.InitAndEnableFeatureWithParameters(dips::kFeature, + features_.InitAndEnableFeatureWithParameters(features::kDIPS, {{"interaction_ttl", "inf"}}); } }; @@ -239,7 +240,7 @@ DIPSDatabaseTest::SetUp(); // Use inf ttl to prevent interactions (including webauthn assertions) from // expiring unintentionally. - features_.InitAndEnableFeatureWithParameters(dips::kFeature, + features_.InitAndEnableFeatureWithParameters(features::kDIPS, {{"interaction_ttl", "inf"}}); } @@ -547,7 +548,7 @@ public testing::WithParamInterface<bool> { public: DIPSDatabaseInteractionTest() : DIPSDatabaseTest(GetParam()) { - features_.InitAndEnableFeature(dips::kFeature); + features_.InitAndEnableFeature(features::kDIPS); } // This test only focuses on user interaction and WAA times, that's the @@ -588,21 +589,22 @@ testing::UnorderedElementsAre("case1.test", "case2.test", "case3.test", "case4.test", "case5.test", "case6.test")); - AdvanceTimeTo(dummy_time + dips::kInteractionTtl.Get()); + AdvanceTimeTo(dummy_time + features::kDIPSInteractionTtl.Get()); EXPECT_EQ(db_->ClearExpiredRows(), 0u); EXPECT_THAT( db_->GetAllSitesForTesting(DIPSDatabaseTable::kBounces), testing::UnorderedElementsAre("case1.test", "case2.test", "case3.test", "case4.test", "case5.test", "case6.test")); - AdvanceTimeTo(dummy_time + dips::kInteractionTtl.Get() + tiny_delta); + AdvanceTimeTo(dummy_time + features::kDIPSInteractionTtl.Get() + tiny_delta); EXPECT_EQ(db_->ClearExpiredRows(), 4u); EXPECT_THAT(db_->GetAllSitesForTesting(DIPSDatabaseTable::kBounces), testing::UnorderedElementsAre("case2.test", "case6.test")); // Time travel to a point by which all interactions and WAAs should've // expired. - AdvanceTimeTo(dummy_time + dips::kInteractionTtl.Get() + tiny_delta * 2); + AdvanceTimeTo(dummy_time + features::kDIPSInteractionTtl.Get() + + tiny_delta * 2); EXPECT_EQ(db_->ClearExpiredRows(), 1u); EXPECT_THAT(db_->GetAllSitesForTesting(DIPSDatabaseTable::kBounces), testing::ElementsAre("case6.test")); @@ -618,7 +620,7 @@ EXPECT_TRUE(db_->Read("case5.test").has_value()); EXPECT_TRUE(db_->Read("case6.test").has_value()); - AdvanceTimeTo(dummy_time + dips::kInteractionTtl.Get()); + AdvanceTimeTo(dummy_time + features::kDIPSInteractionTtl.Get()); EXPECT_TRUE(db_->Read("case1.test").has_value()); EXPECT_TRUE(db_->Read("case2.test").has_value()); EXPECT_TRUE(db_->Read("case3.test").has_value()); @@ -626,7 +628,7 @@ EXPECT_TRUE(db_->Read("case5.test").has_value()); EXPECT_TRUE(db_->Read("case6.test").has_value()); - AdvanceTimeTo(dummy_time + dips::kInteractionTtl.Get() + tiny_delta); + AdvanceTimeTo(dummy_time + features::kDIPSInteractionTtl.Get() + tiny_delta); EXPECT_EQ(db_->Read("case1.test"), absl::nullopt); EXPECT_TRUE(db_->Read("case2.test").has_value()); EXPECT_EQ(db_->Read("case3.test"), absl::nullopt); @@ -636,7 +638,8 @@ // Time travel to a point by which all interactions and WAAs should've // expired. - AdvanceTimeTo(dummy_time + dips::kInteractionTtl.Get() + tiny_delta * 2); + AdvanceTimeTo(dummy_time + features::kDIPSInteractionTtl.Get() + + tiny_delta * 2); EXPECT_EQ(db_->Read("case1.test"), absl::nullopt); EXPECT_EQ(db_->Read("case2.test"), absl::nullopt); EXPECT_EQ(db_->Read("case3.test"), absl::nullopt); @@ -691,41 +694,44 @@ // A test class that verifies the behavior of the methods used to query the // DIPSDatabase to find all sites which should have their state cleared by DIPS. -class DIPSDatabaseQueryTest : public DIPSDatabaseTest, - public testing::WithParamInterface< - std::tuple<bool, DIPSTriggeringAction>> { +class DIPSDatabaseQueryTest + : public DIPSDatabaseTest, + public testing::WithParamInterface< + std::tuple<bool, content::DIPSTriggeringAction>> { public: using QueryMethod = base::RepeatingCallback<std::vector<std::string>(void)>; DIPSDatabaseQueryTest() : DIPSDatabaseTest(std::get<0>(GetParam())) { // Test with the prod feature's parameter to ensure the tested scenarios are // also valid/respected within prod env. - features_.InitAndEnableFeature(dips::kFeature); + features_.InitAndEnableFeature(features::kDIPS); } void SetUp() override { DIPSDatabaseTest::SetUp(); - grace_period = dips::kGracePeriod.Get(); - interaction_ttl = dips::kInteractionTtl.Get(); + grace_period = features::kDIPSGracePeriod.Get(); + interaction_ttl = features::kDIPSInteractionTtl.Get(); } // Returns the DIPS-triggering action we're testing. - DIPSTriggeringAction CurrentAction() { return std::get<1>(GetParam()); } + content::DIPSTriggeringAction CurrentAction() { + return std::get<1>(GetParam()); + } // Returns a callback for the respective querying method we want to test, - // based on `dips::kTriggeringAction`. This is equivalent to that used by - // `DIPSStorage::GetSitesToClear` when the DIPS Timer fires. + // based on `features::kDIPSTriggeringAction`. This is equivalent to that + // used by `DIPSStorage::GetSitesToClear` when the DIPS Timer fires. QueryMethod GetQueryMethodUnderTest() { switch (CurrentAction()) { - case DIPSTriggeringAction::kNone: + case content::DIPSTriggeringAction::kNone: return base::BindLambdaForTesting( [&]() { return std::vector<std::string>{}; }); - case DIPSTriggeringAction::kBounce: + case content::DIPSTriggeringAction::kBounce: return base::BindLambdaForTesting( [&]() { return db_->GetSitesThatBounced(grace_period); }); - case DIPSTriggeringAction::kStorage: + case content::DIPSTriggeringAction::kStorage: return base::BindLambdaForTesting( [&]() { return db_->GetSitesThatUsedStorage(grace_period); }); - case DIPSTriggeringAction::kStatefulBounce: + case content::DIPSTriggeringAction::kStatefulBounce: return base::BindLambdaForTesting( [&]() { return db_->GetSitesThatBouncedWithState(grace_period); }); } @@ -736,19 +742,19 @@ TimestampRange interaction_times, TimestampRange waa_times) { switch (CurrentAction()) { - case DIPSTriggeringAction::kNone: + case content::DIPSTriggeringAction::kNone: break; - case DIPSTriggeringAction::kBounce: + case content::DIPSTriggeringAction::kBounce: db_->Write(site, /*storage_times=*/{}, interaction_times, /*stateful_bounce_times=*/{}, /*bounce_times=*/event_times, waa_times); break; - case DIPSTriggeringAction::kStorage: + case content::DIPSTriggeringAction::kStorage: db_->Write(site, /*storage_times=*/event_times, interaction_times, /*stateful_bounce_times=*/{}, /*bounce_times=*/{}, waa_times); break; - case DIPSTriggeringAction::kStatefulBounce: + case content::DIPSTriggeringAction::kStatefulBounce: db_->Write(site, /*storage_times=*/{}, interaction_times, /*stateful_bounce_times=*/event_times, /*bounce_times=*/event_times, waa_times); @@ -1084,9 +1090,9 @@ DIPSDatabaseQueryTest, ::testing::Combine( ::testing::Bool(), - ::testing::Values(DIPSTriggeringAction::kBounce, - DIPSTriggeringAction::kStorage, - DIPSTriggeringAction::kStatefulBounce))); + ::testing::Values(content::DIPSTriggeringAction::kBounce, + content::DIPSTriggeringAction::kStorage, + content::DIPSTriggeringAction::kStatefulBounce))); // A test class that verifies DIPSDatabase garbage collection behavior for both // tables. @@ -1106,7 +1112,7 @@ void SetUp() override { DIPSDatabaseTest::SetUp(); features_.InitAndEnableFeatureWithParameters( - dips::kFeature, + features::kDIPS, {{"interaction_ttl", base::StringPrintf("%dh", base::Days(90).InHours())}}); @@ -1363,7 +1369,7 @@ void SetUp() override { DIPSDatabaseTest::SetUp(); - features_.InitAndEnableFeatureWithParameters(dips::kFeature, + features_.InitAndEnableFeatureWithParameters(features::kDIPS, {{"interaction_ttl", "inf"}}); } @@ -1448,7 +1454,7 @@ class DIPSDatabaseMigrationTest : public testing::Test { public: DIPSDatabaseMigrationTest() { - features_.InitAndEnableFeatureWithParameters(dips::kFeature, + features_.InitAndEnableFeatureWithParameters(features::kDIPS, {{"interaction_ttl", "inf"}}); }
diff --git a/chrome/browser/dips/dips_features.cc b/chrome/browser/dips/dips_features.cc deleted file mode 100644 index 561d945..0000000 --- a/chrome/browser/dips/dips_features.cc +++ /dev/null
@@ -1,70 +0,0 @@ -// Copyright 2022 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/dips/dips_features.h" - -#include "base/feature_list.h" -#include "base/time/time.h" - -namespace dips { - -// Enables the DIPS (Detect Incidental Party State) feature. -// On by default to allow for collecting metrics. All potentially dangerous -// behavior (database persistence, DIPS deletion) will be gated by params. -BASE_FEATURE(kFeature, "DIPS", base::FEATURE_ENABLED_BY_DEFAULT); - -// Set whether DIPS persists its database to disk. -const base::FeatureParam<bool> kPersistedDatabaseEnabled{ - &kFeature, "persist_database", true}; - -// Set whether DIPS performs deletion. -const base::FeatureParam<bool> kDeletionEnabled{&kFeature, "delete", false}; - -// Set the time period that Chrome will wait for before clearing storage for a -// site after it performs some action (e.g. bouncing the user or using storage) -// without user interaction. -const base::FeatureParam<base::TimeDelta> kGracePeriod{ - &kFeature, "grace_period", base::Hours(1)}; - -// Set the cadence at which Chrome will attempt to clear incidental state -// repeatedly. -const base::FeatureParam<base::TimeDelta> kTimerDelay{&kFeature, "timer_delay", - base::Hours(1)}; - -// Sets how long DIPS maintains interactions and Web Authn Assertions (WAA) for -// a site. -// -// If a site in the DIPS database has an interaction or WAA within the grace -// period a DIPS-triggering action, then that action and all ensuing actions are -// protected from DIPS clearing until the interaction and WAA "expire" as set -// by this param. -// NOTE: Updating this param name (to reflect WAA) is deemed unnecessary as far -// as readability is concerned. -const base::FeatureParam<base::TimeDelta> kInteractionTtl{ - &kFeature, "interaction_ttl", base::Days(45)}; - -constexpr base::FeatureParam<DIPSTriggeringAction>::Option - kTriggeringActionOptions[] = { - {DIPSTriggeringAction::kNone, "none"}, - {DIPSTriggeringAction::kStorage, "storage"}, - {DIPSTriggeringAction::kBounce, "bounce"}, - {DIPSTriggeringAction::kStatefulBounce, "stateful_bounce"}}; - -// Sets the actions which will trigger DIPS clearing for a site. The default is -// to set to kBounce, but can be overridden by Finch experiment groups, -// command-line flags, or chrome flags. -// -// Note: Maintain a matching nomenclature of the options with the feature flag -// entries at about_flags.cc. -const base::FeatureParam<DIPSTriggeringAction> kTriggeringAction{ - &kFeature, "triggering_action", DIPSTriggeringAction::kNone, - &kTriggeringActionOptions}; - -// Denotes the length of a time interval within which any client-side redirect -// is viewed as a bounce (provided all other criteria are equally met). The -// interval starts every time a page finishes a navigation (a.k.a. a commit is -// registered). -const base::FeatureParam<base::TimeDelta> kClientBounceDetectionTimeout{ - &kFeature, "client_bounce_detection_timeout", base::Seconds(10)}; -} // namespace dips
diff --git a/chrome/browser/dips/dips_features.h b/chrome/browser/dips/dips_features.h deleted file mode 100644 index 7f62d55..0000000 --- a/chrome/browser/dips/dips_features.h +++ /dev/null
@@ -1,25 +0,0 @@ -// Copyright 2022 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_DIPS_DIPS_FEATURES_H_ -#define CHROME_BROWSER_DIPS_DIPS_FEATURES_H_ - -#include "base/feature_list.h" -#include "base/metrics/field_trial_params.h" -#include "base/time/time.h" -#include "chrome/browser/dips/dips_utils.h" - -namespace dips { - -BASE_DECLARE_FEATURE(kFeature); -extern const base::FeatureParam<bool> kPersistedDatabaseEnabled; -extern const base::FeatureParam<bool> kDeletionEnabled; -extern const base::FeatureParam<base::TimeDelta> kGracePeriod; -extern const base::FeatureParam<base::TimeDelta> kTimerDelay; -extern const base::FeatureParam<base::TimeDelta> kInteractionTtl; -extern const base::FeatureParam<DIPSTriggeringAction> kTriggeringAction; -extern const base::FeatureParam<base::TimeDelta> kClientBounceDetectionTimeout; -} // namespace dips - -#endif // CHROME_BROWSER_DIPS_DIPS_FEATURES_H_
diff --git a/chrome/browser/dips/dips_helper_browsertest.cc b/chrome/browser/dips/dips_helper_browsertest.cc index 3aa15ae..f240fe9b 100644 --- a/chrome/browser/dips/dips_helper_browsertest.cc +++ b/chrome/browser/dips/dips_helper_browsertest.cc
@@ -19,7 +19,6 @@ #include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/dips/dips_bounce_detector.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_service.h" #include "chrome/browser/dips/dips_service_factory.h" #include "chrome/browser/dips/dips_storage.h" @@ -34,6 +33,7 @@ #include "content/public/browser/browser_context.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_observer.h" +#include "content/public/common/content_features.h" #include "content/public/test/browser_task_environment.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" @@ -77,11 +77,11 @@ void SetUp() override { if (IsPersistentStorageEnabled()) { scoped_feature_list_.InitAndEnableFeatureWithParameters( - dips::kFeature, + features::kDIPS, {{"persist_database", "true"}, {"triggering_action", "bounce"}}); } else { scoped_feature_list_.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"triggering_action", "bounce"}}); + features::kDIPS, {{"triggering_action", "bounce"}}); } PlatformBrowserTest::SetUp(); } @@ -687,10 +687,10 @@ if (content::IsPreTest() && GetTestPreCount() % 2 != 0) { // Alternate between disabling and enabling DIPS in `PRE_` tests. // Only disable explicitly since the feature is on by default. - feature_list_.InitAndDisableFeature(dips::kFeature); + feature_list_.InitAndDisableFeature(features::kDIPS); } else { feature_list_.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"persist_database", "true"}}); + features::kDIPS, {{"persist_database", "true"}}); } PlatformBrowserTest::SetUp(); @@ -932,7 +932,7 @@ .has_value()); // Trigger the DIPS timer which will delete tracker data. - SetDIPSTime(recent_bounce_time + dips::kGracePeriod.Get() + + SetDIPSTime(recent_bounce_time + features::kDIPSGracePeriod.Get() + base::Milliseconds(1)); dips_service->OnTimerFiredForTesting(); dips_service->storage()->FlushPostedTasksForTesting(); @@ -1002,7 +1002,8 @@ *new_tab, embedded_test_server()->GetURL("c.test", "/title1.html"))); // Trigger the DIPS timer which would delete tracker data. - SetDIPSTime(bounce_time + dips::kGracePeriod.Get() + base::Milliseconds(1)); + SetDIPSTime(bounce_time + features::kDIPSGracePeriod.Get() + + base::Milliseconds(1)); dips_service->OnTimerFiredForTesting(); dips_service->storage()->FlushPostedTasksForTesting(); base::RunLoop().RunUntilIdle(); @@ -1055,7 +1056,8 @@ CloseTab(*new_tab); // Trigger the DIPS timer which would delete tracker data. - SetDIPSTime(bounce_time + dips::kGracePeriod.Get() + base::Milliseconds(1)); + SetDIPSTime(bounce_time + features::kDIPSGracePeriod.Get() + + base::Milliseconds(1)); dips_service->OnTimerFiredForTesting(); dips_service->storage()->FlushPostedTasksForTesting(); base::RunLoop().RunUntilIdle(); @@ -1106,7 +1108,8 @@ ASSERT_TRUE(ui_test_utils::NavigateToURL(new_browser, GURL("http://c.test"))); // Trigger the DIPS timer which would delete tracker data. - SetDIPSTime(bounce_time + dips::kGracePeriod.Get() + base::Milliseconds(1)); + SetDIPSTime(bounce_time + features::kDIPSGracePeriod.Get() + + base::Milliseconds(1)); dips_service->OnTimerFiredForTesting(); dips_service->storage()->FlushPostedTasksForTesting(); base::RunLoop().RunUntilIdle();
diff --git a/chrome/browser/dips/dips_service.cc b/chrome/browser/dips/dips_service.cc index 46c0ccfb..e761e11 100644 --- a/chrome/browser/dips/dips_service.cc +++ b/chrome/browser/dips/dips_service.cc
@@ -21,7 +21,6 @@ #include "chrome/browser/content_settings/cookie_settings_factory.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/dips/dips_browser_signin_detector.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_redirect_info.h" #include "chrome/browser/dips/dips_service_factory.h" #include "chrome/browser/dips/dips_storage.h" @@ -37,6 +36,8 @@ #include "content/public/browser/browser_thread.h" #include "content/public/browser/browsing_data_filter_builder.h" #include "content/public/browser/browsing_data_remover.h" +#include "content/public/common/content_features.h" +#include "content/public/common/dips_utils.h" #include "net/cookies/cookie_setting_override.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" @@ -180,7 +181,7 @@ cookie_settings_(CookieSettingsFactory::GetForProfile( Profile::FromBrowserContext(context))), repeating_timer_(CreateTimer(Profile::FromBrowserContext(context))) { - DCHECK(base::FeatureList::IsEnabled(dips::kFeature)); + DCHECK(base::FeatureList::IsEnabled(features::kDIPS)); absl::optional<base::FilePath> path_to_use; base::FilePath dips_path = GetDIPSFilePath(browser_context_); @@ -191,7 +192,7 @@ // profile. wait_for_file_deletion_.Quit(); } else { - if (dips::kPersistedDatabaseEnabled.Get()) { + if (features::kDIPSPersistedDatabaseEnabled.Get()) { path_to_use = dips_path; // Existing database files won't be deleted, so quit the // `wait_for_file_deletion_` RunLoop. @@ -224,7 +225,8 @@ // base::Unretained(this) is safe here since the timer that is created has the // same lifetime as this service. return std::make_unique<signin::PersistentRepeatingTimer>( - profile->GetPrefs(), prefs::kDIPSTimerLastUpdate, dips::kTimerDelay.Get(), + profile->GetPrefs(), prefs::kDIPSTimerLastUpdate, + features::kDIPSTimerDelay.Get(), base::BindRepeating(&DIPSService::OnTimerFired, base::Unretained(this))); } @@ -375,7 +377,8 @@ // These records indicate sites that could've had their state deleted // provided their grace period expired. But are at the moment excepted // following `Are3PCAllowed()` of either `initial_url` or `final_url`. - if ((dips::kTriggeringAction.Get() == DIPSTriggeringAction::kStatefulBounce + if ((features::kDIPSTriggeringAction.Get() == + content::DIPSTriggeringAction::kStatefulBounce ? stateful : true)) { // TODO(crbug.com/1447035): Investigate and fix the presence of empty @@ -511,11 +514,11 @@ // meantime). .SetShouldBlockThirdPartyCookies(true) .SetHasCookieException(false) - .SetIsDeletionEnabled(dips::kDeletionEnabled.Get()) + .SetIsDeletionEnabled(features::kDIPSDeletionEnabled.Get()) .Record(ukm::UkmRecorder::Get()); } - if (dips::kDeletionEnabled.Get()) { + if (features::kDIPSDeletionEnabled.Get()) { std::vector<std::string> filtered_sites_to_clear; for (const auto& site : sites_to_clear) {
diff --git a/chrome/browser/dips/dips_service_factory.cc b/chrome/browser/dips/dips_service_factory.cc index f9766d5..7992ea4 100644 --- a/chrome/browser/dips/dips_service_factory.cc +++ b/chrome/browser/dips/dips_service_factory.cc
@@ -6,9 +6,9 @@ #include "base/no_destructor.h" #include "chrome/browser/content_settings/cookie_settings_factory.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_service.h" #include "chrome/browser/signin/identity_manager_factory.h" +#include "content/public/common/content_features.h" /* static */ DIPSService* DIPSServiceFactory::GetForBrowserContext( @@ -24,7 +24,7 @@ /* static */ ProfileSelections DIPSServiceFactory::CreateProfileSelections() { - if (!base::FeatureList::IsEnabled(dips::kFeature)) { + if (!base::FeatureList::IsEnabled(features::kDIPS)) { return ProfileSelections::BuildNoProfilesSelected(); }
diff --git a/chrome/browser/dips/dips_service_unittest.cc b/chrome/browser/dips/dips_service_unittest.cc index b70fddb..f20fa88 100644 --- a/chrome/browser/dips/dips_service_unittest.cc +++ b/chrome/browser/dips/dips_service_unittest.cc
@@ -18,7 +18,6 @@ #include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h" #include "chrome/browser/content_settings/cookie_settings_factory.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_redirect_info.h" #include "chrome/browser/dips/dips_service_factory.h" #include "chrome/browser/dips/dips_state.h" @@ -32,6 +31,7 @@ #include "components/content_settings/core/common/pref_names.h" #include "components/prefs/pref_service.h" #include "components/ukm/test_ukm_recorder.h" +#include "content/public/common/content_features.h" #include "content/public/test/browser_task_environment.h" #include "content/public/test/mock_browsing_data_remover_delegate.h" #include "services/metrics/public/cpp/ukm_source_id.h" @@ -70,7 +70,7 @@ // Ensure the DIPS feature is enabled and the database is set to be persisted. base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"persist_database", "true"}}); + features::kDIPS, {{"persist_database", "true"}}); profile = TestingProfile::Builder().SetPath(data_path).Build(); service = DIPSService::Get(profile.get()); @@ -85,7 +85,7 @@ // Reset the feature list to set database persistence to false. feature_list.Reset(); feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"persist_database", "false"}}); + features::kDIPS, {{"persist_database", "false"}}); // Reset the TestingProfile, then create a new instance with the same user // data path. @@ -109,7 +109,7 @@ // Ensure the DIPS feature is enabled and the database is set to be persisted. base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"persist_database", "true"}}); + features::kDIPS, {{"persist_database", "true"}}); // Build a regular profile. std::unique_ptr<TestingProfile> profile = @@ -141,7 +141,7 @@ TEST_F(DIPSServiceTest, EmptySiteEventsIgnored) { base::test::ScopedFeatureList feature_list; - feature_list.InitAndEnableFeature(dips::kFeature); + feature_list.InitAndEnableFeature(features::kDIPS); std::unique_ptr<TestingProfile> profile = std::make_unique<TestingProfile>(); DIPSService* service = DIPSService::Get(profile.get()); @@ -195,8 +195,8 @@ // Test setup. void SetUp() override { - grace_period = dips::kGracePeriod.Get(); - interaction_ttl = dips::kInteractionTtl.Get(); + grace_period = features::kDIPSGracePeriod.Get(); + interaction_ttl = features::kDIPSInteractionTtl.Get(); ASSERT_LT(tiny_delta, grace_period); GetProfile()->GetBrowsingDataRemover()->SetEmbedderDelegate(&delegate_); @@ -333,7 +333,7 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"delete", "true"}, {"triggering_action", "bounce"}}); + features::kDIPS, {{"delete", "true"}, {"triggering_action", "bounce"}}); // Record a bounce. GURL url("https://example.com"); @@ -383,7 +383,7 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"delete", "false"}, {"triggering_action", "bounce"}}); + features::kDIPS, {{"delete", "false"}, {"triggering_action", "bounce"}}); // Record a bounce. GURL url("https://example.com"); @@ -410,8 +410,8 @@ task_environment_.RunUntilIdle(); // Verify that the site's DIPS entry WAS removed, but a removal task was NOT - // posted to the BrowsingDataRemover(Delegate) since `dips::kDeletionEnabled` - // is false. + // posted to the BrowsingDataRemover(Delegate) since + // `features::kDIPSDeletionEnabled` is false. delegate_.VerifyAndClearExpectations(); EXPECT_FALSE(GetDIPSState(GetService(), url).has_value()); @@ -424,7 +424,7 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"delete", "true"}, {"triggering_action", "bounce"}}); + features::kDIPS, {{"delete", "true"}, {"triggering_action", "bounce"}}); GURL excepted_3p_url("https://excepted-as-3p.com"); GURL non_excepted_url("https://not-excepted.com"); @@ -469,7 +469,7 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"delete", "true"}, {"triggering_action", "bounce"}}); + features::kDIPS, {{"delete", "true"}, {"triggering_action", "bounce"}}); GURL excepted_1p_url("https://excepted-as-1p.com"); GURL scoped_excepted_1p_url("https://excepted-as-1p-with-3p.com"); @@ -549,7 +549,7 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; std::vector<base::test::FeatureRefAndParams> enabled_features; enabled_features.push_back( - {dips::kFeature, {{"delete", "true"}, {"triggering_action", "bounce"}}}); + {features::kDIPS, {{"delete", "true"}, {"triggering_action", "bounce"}}}); enabled_features.push_back({blink::features::kStorageAccessAPI, {}}); base::test::ScopedFeatureList feature_list; feature_list.InitWithFeaturesAndParameters(enabled_features, {}); @@ -644,7 +644,7 @@ ukm::TestAutoSetUkmRecorder ukm_recorder; base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"delete", "true"}, {"triggering_action", "bounce"}}); + features::kDIPS, {{"delete", "true"}, {"triggering_action", "bounce"}}); GURL blocked_1p_url("https://excepted-as-1p.com"); GURL scoped_blocked_1p_url("https://excepted-as-1p-with-3p.com"); @@ -728,7 +728,7 @@ TEST_F(DIPSServiceStateRemovalTest, ImmediateEnforcement) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"delete", "true"}, {"triggering_action", "bounce"}}); + features::kDIPS, {{"delete", "true"}, {"triggering_action", "bounce"}}); SetNow(base::Time::FromDoubleT(2)); // Record a bounce. @@ -797,7 +797,7 @@ TEST_F(DIPSServiceHistogramTest, DeletionLatency) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"delete", "false"}, {"triggering_action", "bounce"}}); + features::kDIPS, {{"delete", "false"}, {"triggering_action", "bounce"}}); // Verify the histogram starts empty histograms().ExpectTotalCount("Privacy.DIPS.DeletionLatency2", 0); @@ -834,7 +834,7 @@ TEST_F(DIPSServiceHistogramTest, Deletion_Disallowed) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, + features::kDIPS, {{"delete", "false"}, {"triggering_action", "stateful_bounce"}}); // Verify the histogram is initially empty. @@ -868,7 +868,7 @@ TEST_F(DIPSServiceHistogramTest, Deletion_ExceptedAs1P) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, + features::kDIPS, {{"delete", "true"}, {"triggering_action", "stateful_bounce"}}); // Verify the histogram is initially empty. @@ -904,7 +904,7 @@ TEST_F(DIPSServiceHistogramTest, Deletion_ExceptedAs3P) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, + features::kDIPS, {{"delete", "true"}, {"triggering_action", "stateful_bounce"}}); // Verify the histogram is initially empty. @@ -939,7 +939,7 @@ TEST_F(DIPSServiceHistogramTest, Deletion_Enforced) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeatureWithParameters( - dips::kFeature, + features::kDIPS, {{"delete", "true"}, {"triggering_action", "stateful_bounce"}}); // Verify the histogram is initially empty.
diff --git a/chrome/browser/dips/dips_storage.cc b/chrome/browser/dips/dips_storage.cc index dcb6e46..93d42e4 100644 --- a/chrome/browser/dips/dips_storage.cc +++ b/chrome/browser/dips/dips_storage.cc
@@ -13,8 +13,9 @@ #include "base/task/sequenced_task_runner.h" #include "base/task/thread_pool.h" #include "base/threading/thread_restrictions.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_utils.h" +#include "content/public/common/content_features.h" +#include "content/public/common/dips_utils.h" #include "services/network/public/mojom/clear_data_filter.mojom.h" #include "services/network/public/mojom/network_context.mojom.h" #include "url/gurl.h" @@ -271,21 +272,21 @@ absl::optional<base::TimeDelta> custom_period) const { std::vector<std::string> sites_to_clear; base::TimeDelta grace_period = - custom_period.value_or(dips::kGracePeriod.Get()); + custom_period.value_or(features::kDIPSGracePeriod.Get()); - switch (dips::kTriggeringAction.Get()) { - case DIPSTriggeringAction::kNone: { + switch (features::kDIPSTriggeringAction.Get()) { + case content::DIPSTriggeringAction::kNone: { return {}; } - case DIPSTriggeringAction::kStorage: { + case content::DIPSTriggeringAction::kStorage: { sites_to_clear = GetSitesThatUsedStorage(grace_period); break; } - case DIPSTriggeringAction::kBounce: { + case content::DIPSTriggeringAction::kBounce: { sites_to_clear = GetSitesThatBounced(grace_period); break; } - case DIPSTriggeringAction::kStatefulBounce: { + case content::DIPSTriggeringAction::kStatefulBounce: { sites_to_clear = GetSitesThatBouncedWithState(grace_period); break; }
diff --git a/chrome/browser/dips/dips_storage.h b/chrome/browser/dips/dips_storage.h index df3c268..e8b2c0c 100644 --- a/chrome/browser/dips/dips_storage.h +++ b/chrome/browser/dips/dips_storage.h
@@ -76,8 +76,9 @@ // Returns the list of sites that should have their state cleared by DIPS. How // these sites are determined is controlled by the value of - // `dips::kTriggeringAction`. Passing a non-NULL `grace_period` parameter - // overrides the use of `dips::kGracePeriod` when evaluating sites to clear. + // `features::kDIPSTriggeringAction`. Passing a non-NULL `grace_period` + // parameter overrides the use of `features::kDIPSGracePeriod` when + // evaluating sites to clear. std::vector<std::string> GetSitesToClear( absl::optional<base::TimeDelta> grace_period) const;
diff --git a/chrome/browser/dips/dips_storage_unittest.cc b/chrome/browser/dips/dips_storage_unittest.cc index 39de0aef..28047ae8 100644 --- a/chrome/browser/dips/dips_storage_unittest.cc +++ b/chrome/browser/dips/dips_storage_unittest.cc
@@ -14,10 +14,10 @@ #include "base/test/task_environment.h" #include "base/threading/sequence_bound.h" #include "base/time/time.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_state.h" #include "chrome/browser/dips/dips_utils.h" #include "content/public/browser/browsing_data_filter_builder.h" +#include "content/public/common/content_features.h" #include "services/network/public/mojom/clear_data_filter.mojom.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -51,7 +51,7 @@ public: explicit ScopedDIPSFeatureEnabledWithParams( const base::FieldTrialParams& params) { - features_.InitAndEnableFeatureWithParameters(dips::kFeature, params); + features_.InitAndEnableFeatureWithParameters(features::kDIPS, params); } private: @@ -78,14 +78,14 @@ // Call 'GetSitesToClear' when the trigger is unset. { base::test::ScopedFeatureList features; - features.InitAndEnableFeature(dips::kFeature); + features.InitAndEnableFeature(features::kDIPS); EXPECT_THAT(storage.GetSitesToClear(absl::nullopt), testing::IsEmpty()); } // Call 'GetSitesToClear' when DIPS is triggered by bounces. { base::test::ScopedFeatureList features; features.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"triggering_action", "bounce"}}); + features::kDIPS, {{"triggering_action", "bounce"}}); EXPECT_THAT(storage.GetSitesToClear(absl::nullopt), testing::ElementsAre(GetSiteForDIPS(kBounceUrl), GetSiteForDIPS(kStatefulBounceUrl))); @@ -94,7 +94,7 @@ { base::test::ScopedFeatureList features; features.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"triggering_action", "storage"}}); + features::kDIPS, {{"triggering_action", "storage"}}); EXPECT_THAT(storage.GetSitesToClear(absl::nullopt), testing::ElementsAre(GetSiteForDIPS(kStatefulBounceUrl), GetSiteForDIPS(kStorageUrl))); @@ -103,7 +103,7 @@ { base::test::ScopedFeatureList features; features.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"triggering_action", "stateful_bounce"}}); + features::kDIPS, {{"triggering_action", "stateful_bounce"}}); EXPECT_THAT(storage.GetSitesToClear(absl::nullopt), testing::ElementsAre(GetSiteForDIPS(kStatefulBounceUrl))); } @@ -135,12 +135,12 @@ base::test::ScopedFeatureList features; features.InitAndEnableFeatureWithParameters( - dips::kFeature, + features::kDIPS, {{"grace_period", "30s"}, {"triggering_action", "stateful_bounce"}}); - // Advance time by less than `dips::kGracePeriod` but greater than `start + - // custom_grace_period` and verify that no sites are returned without using - // the custom grace period. + // Advance time by less than `features::kDIPSGracePeriod` but greater than + // `start + custom_grace_period` and verify that no sites are returned without + // using the custom grace period. clock.Advance(base::Seconds(10)); EXPECT_THAT(storage.GetSitesToClear(absl::nullopt), testing::IsEmpty()); // Verify that using a custom grace period less than the amount time was @@ -174,10 +174,10 @@ // unset. { base::test::ScopedFeatureList features; - features.InitAndEnableFeature(dips::kFeature); - // Advance time by less than `dips::kGracePeriod` and verify that no sites - // are returned - clock.Advance(dips::kGracePeriod.Get() / 2); + features.InitAndEnableFeature(features::kDIPS); + // Advance time by less than `features::kDIPSGracePeriod` and verify that + // no sites are returned + clock.Advance(features::kDIPSGracePeriod.Get() / 2); EXPECT_THAT(storage.GetSitesToClear(absl::nullopt), testing::IsEmpty()); // Verify that using a custom grace period less than the amount time was // advanced still returns nothing when the trigger is unset. @@ -192,10 +192,10 @@ { base::test::ScopedFeatureList features; features.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"triggering_action", "bounce"}}); - // Advance time by less than `dips::kGracePeriod` and verify that no sites - // are returned without using a custom grace period. - clock.Advance(dips::kGracePeriod.Get() / 2); + features::kDIPS, {{"triggering_action", "bounce"}}); + // Advance time by less than `features::kDIPSGracePeriod` and verify that + // no sites are returned without using a custom grace period. + clock.Advance(features::kDIPSGracePeriod.Get() / 2); EXPECT_THAT(storage.GetSitesToClear(absl::nullopt), testing::IsEmpty()); // Verify that using a custom grace period less than the amount time was // advanced returns the expected sites for triggering on bounces. @@ -212,10 +212,10 @@ { base::test::ScopedFeatureList features; features.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"triggering_action", "storage"}}); - // Advance time by less than `dips::kGracePeriod` and verify that no sites - // are returned without using a custom grace period. - clock.Advance(dips::kGracePeriod.Get() / 2); + features::kDIPS, {{"triggering_action", "storage"}}); + // Advance time by less than `features::kDIPSGracePeriod` and verify that + // no sites are returned without using a custom grace period. + clock.Advance(features::kDIPSGracePeriod.Get() / 2); EXPECT_THAT(storage.GetSitesToClear(absl::nullopt), testing::IsEmpty()); // Verify that using a custom grace period less than the amount time was // advanced returns the expected sites for triggering on storage. @@ -232,10 +232,10 @@ { base::test::ScopedFeatureList features; features.InitAndEnableFeatureWithParameters( - dips::kFeature, {{"triggering_action", "stateful_bounce"}}); - // Advance time by less than `dips::kGracePeriod` and verify that no sites - // are returned without using a custom grace period. - clock.Advance(dips::kGracePeriod.Get() / 2); + features::kDIPS, {{"triggering_action", "stateful_bounce"}}); + // Advance time by less than `features::kDIPSGracePeriod` and verify that + // no sites are returned without using a custom grace period. + clock.Advance(features::kDIPSGracePeriod.Get() / 2); EXPECT_THAT(storage.GetSitesToClear(absl::nullopt), testing::IsEmpty()); // Verify that using a custom grace period less than the amount time was // advanced returns the expected sites for triggering on stateful bounces.
diff --git a/chrome/browser/dips/dips_test_utils.cc b/chrome/browser/dips/dips_test_utils.cc index 792e912..2029ca7c 100644 --- a/chrome/browser/dips/dips_test_utils.cc +++ b/chrome/browser/dips/dips_test_utils.cc
@@ -6,9 +6,9 @@ #include "base/test/bind.h" #include "chrome/browser/dips/dips_cleanup_service_factory.h" -#include "chrome/browser/dips/dips_features.h" #include "chrome/browser/dips/dips_service_factory.h" #include "content/public/browser/web_contents.h" +#include "content/public/common/content_features.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/test_utils.h" #include "testing/gmock/include/gmock/gmock.h" @@ -218,7 +218,7 @@ const base::FieldTrialParams& params) // DIPSServiceFactory and DIPSCleanupServiceFactory are singletons, and we // want to create them *before* constructing `init_feature_`, so that they - // are initialized using the default value of dips::kFeature. We only want + // are initialized using the default value of features::kDIPS. We only want // `init_feature_` to affect CreateProfileSelections(). We do this // concisely by using the comma operator in the arguments to // `init_feature_` to call DIPSServiceFactory::GetInstance() and @@ -226,7 +226,7 @@ // values. : init_feature_((DIPSServiceFactory::GetInstance(), DIPSCleanupServiceFactory::GetInstance(), - dips::kFeature), + features::kDIPS), enable, params), override_profile_selections_for_dips_service_(
diff --git a/chrome/browser/dips/dips_utils.h b/chrome/browser/dips/dips_utils.h index f693a96..ff45925 100644 --- a/chrome/browser/dips/dips_utils.h +++ b/chrome/browser/dips/dips_utils.h
@@ -50,8 +50,8 @@ base::FilePath GetDIPSFilePath(content::BrowserContext* context); // The ProfileSelections used to dictate when the DIPSService should be created, -// if `dips::kFeature` is enabled, and when the DIPSCleanupService should be -// created, if `dips::kFeature` is NOT enabled. +// if `features::kDIPS` is enabled, and when the DIPSCleanupService +// should be created, if `features::kDIPS` is NOT enabled. ProfileSelections GetHumanProfileSelections(); // SiteDataAccessType: @@ -158,8 +158,6 @@ rhs.web_authn_assertion_times); } -enum class DIPSTriggeringAction { kNone, kStorage, kBounce, kStatefulBounce }; - // Return the number of seconds in `td`, clamped to [0, 10]. // i.e. 11 linearly-sized buckets. int64_t BucketizeBounceDelay(base::TimeDelta delta);
diff --git a/chrome/browser/download/download_item_model_unittest.cc b/chrome/browser/download/download_item_model_unittest.cc index b7bfa4a..ebf7a6d 100644 --- a/chrome/browser/download/download_item_model_unittest.cc +++ b/chrome/browser/download/download_item_model_unittest.cc
@@ -618,7 +618,7 @@ {download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK, "Blocked by your organization"}, {download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING, - "Scan before opening"}, + "Scan for malware \xE2\x80\xA2 Suspicious"}, }; for (const auto& test_case : kDangerTypeTestCases) { SetupDownloadItemDefaults(); @@ -756,13 +756,14 @@ {DownloadCommands::Command::DISCARD, DownloadCommands::Command::KEEP}}, {download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING, false, - DownloadCommands::Command::DEEP_SCAN, + absl::nullopt, {DownloadCommands::Command::DEEP_SCAN, DownloadCommands::Command::BYPASS_DEEP_SCANNING}}, {download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING, false, - DownloadCommands::Command::BYPASS_DEEP_SCANNING, - {}}, + absl::nullopt, + {DownloadCommands::Command::DISCARD, + DownloadCommands::Command::CANCEL_DEEP_SCAN}}, }; for (const auto& test_case : kDangerTypeTestCases) { SCOPED_TRACE(testing::Message() @@ -824,13 +825,14 @@ {DownloadCommands::Command::DISCARD, DownloadCommands::Command::KEEP}}, {download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING, false, - DownloadCommands::Command::DEEP_SCAN, + absl::nullopt, {DownloadCommands::Command::DEEP_SCAN, DownloadCommands::Command::BYPASS_DEEP_SCANNING}}, {download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING, false, - DownloadCommands::Command::BYPASS_DEEP_SCANNING, - {}}, + absl::nullopt, + {DownloadCommands::Command::DISCARD, + DownloadCommands::Command::CANCEL_DEEP_SCAN}}, }; for (const auto& test_case : kDangerTypeTestCases) { SCOPED_TRACE(testing::Message()
diff --git a/chrome/browser/download/internal/android/java/res/values/dimens.xml b/chrome/browser/download/internal/android/java/res/values/dimens.xml index a9f6fd3c..618aca7 100644 --- a/chrome/browser/download/internal/android/java/res/values/dimens.xml +++ b/chrome/browser/download/internal/android/java/res/values/dimens.xml
@@ -22,4 +22,7 @@ <!-- Download interstitial dimensions --> <dimen name="download_interstitial_horizontal_padding">24dp</dimen> + + <!-- Download_storage_summary height(32dp) = text height(16) + top padding(8) + bottom padding(8) --> + <dimen name="download_storage_summary_height">32dp</dimen> </resources>
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListCoordinator.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListCoordinator.java index 5ad92f8..dca89ba 100644 --- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListCoordinator.java +++ b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListCoordinator.java
@@ -4,13 +4,16 @@ package org.chromium.chrome.browser.download.home.list; +import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.widget.FrameLayout; +import android.widget.ScrollView; import org.chromium.base.Callback; import org.chromium.base.DiscardableReferencePool; @@ -27,6 +30,7 @@ import org.chromium.chrome.browser.download.home.storage.StorageCoordinator; import org.chromium.chrome.browser.download.home.toolbar.ToolbarCoordinator; import org.chromium.chrome.browser.download.internal.R; +import org.chromium.components.browser_ui.util.DimensionCompat; import org.chromium.components.browser_ui.widget.gesture.BackPressHandler; import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate; import org.chromium.components.offline_items_collection.OfflineContentProvider; @@ -87,6 +91,10 @@ private final DateOrderedListView mListView; private final RenameDialogManager mRenameDialogManager; private ViewGroup mMainView; + private View mEmptyView; + private int mWindowHeight; + private int mDownloadStorageSummaryHeightPx; + private int mSelectableListToolbarHeightPx; /** * Creates an instance of a DateOrderedListCoordinator, which will visually represent @@ -116,8 +124,8 @@ ListItemModel model = new ListItemModel(); DecoratedListItemModel decoratedModel = new DecoratedListItemModel(model); - mListView = - new DateOrderedListView(context, config, decoratedModel, dateOrderedListObserver); + mListView = new DateOrderedListView(context, config, decoratedModel, + dateOrderedListObserver, this::onConfigurationChangedCallback); mRenameDialogManager = new RenameDialogManager(context, modalDialogManager); mMediator = new DateOrderedListMediator(provider, faviconProvider, this::startShareIntent, deleteController, this::startRename, selectionDelegate, config, @@ -137,9 +145,60 @@ new ViewListItem(StableIds.STORAGE_HEADER, mStorageCoordinator.getView())); decoratedModel.addHeader( new ViewListItem(StableIds.FILTERS_HEADER, mFilterCoordinator.getView())); + mWindowHeight = getWindowHeight(); + + mDownloadStorageSummaryHeightPx = (int) (mContext.getResources().getDimensionPixelSize( + R.dimen.download_storage_summary_height)); + mSelectableListToolbarHeightPx = mContext.getResources().getDimensionPixelSize( + R.dimen.selectable_list_toolbar_height); initializeView(context); } + protected void onConfigurationChangedCallback() { + mWindowHeight = getWindowHeight(); + + // Update empty view margin when configuration changes. + addMarginOnConfigurationChanged(); + } + + private int getWindowHeight() { + return DimensionCompat.create((Activity) mContext, null).getWindowHeight(); + } + + /** + * We need to apply mDownloadStorageSummaryHeightPx as top margin to ensure empty view don't + * scroll above download storage summary, and add the same offset in the bottom margin to + * center the empty view. But we shouldn't apply bottom margin to avoid empty view text get + * cut off in small screen(when window height is smaller than maxEmptyHeight). + */ + private void addMarginOnConfigurationChanged() { + if (mEmptyView == null || mEmptyView.findViewById(R.id.empty_state_container) == null) { + return; + } + ViewGroup emptyScrollView = mEmptyView.findViewById(R.id.empty_state_container); + FrameLayout.LayoutParams layoutParams = + (FrameLayout.LayoutParams) mEmptyView.getLayoutParams(); + mWindowHeight = getWindowHeight(); + + // Adding margin to make sure empty view is centered from top of toolbar and not overlap + // with download storage when screen size become small. + int topMargin = mDownloadStorageSummaryHeightPx; + int bottomMargin = mDownloadStorageSummaryHeightPx; + + // Height from the toolbar to the bottom of empty view. + int maxEmptyHeight = ((ScrollView) emptyScrollView).getChildAt(0).getHeight() + + mSelectableListToolbarHeightPx + bottomMargin + topMargin; + + if (mWindowHeight <= maxEmptyHeight) { + layoutParams.setMargins(0, topMargin, 0, 0); + layoutParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + } else { + layoutParams.setMargins(0, topMargin, 0, bottomMargin); + layoutParams.gravity = Gravity.CENTER; + } + mEmptyView.setLayoutParams(layoutParams); + } + /** * Creates a top-level view containing the {@link DateOrderedListView} and {@link EmptyView}. * The list view is added on top of the empty view so that the empty view will show up when the @@ -148,30 +207,32 @@ */ private void initializeView(Context context) { mMainView = new FrameLayout(context); + mEmptyView = mEmptyCoordinator.getView(); FrameLayout.LayoutParams emptyViewParams; emptyViewParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); emptyViewParams.gravity = Gravity.CENTER; - // download_storage_summary height = text height(16) + text leading(16) + top padding(8) + - // bottom padding(8) = 48 dp - int downloadStorageSummaryHeightPx = - (int) (48 * context.getResources().getDisplayMetrics().density); + // Handle empty view position in the first run. + mEmptyView.getViewTreeObserver().addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // Add margin depends on screen orientation. + addMarginOnConfigurationChanged(); - int bottomMargin = mContext.getResources().getDimensionPixelSize( - R.dimen.selectable_list_toolbar_height) - / 2; - - // Adding margin to make sure empty view is centered from top of toolbar and not overlap - // with download storage when screen size become small. - emptyViewParams.bottomMargin = bottomMargin + (downloadStorageSummaryHeightPx / 2); - emptyViewParams.topMargin = downloadStorageSummaryHeightPx / 2; - - mMainView.addView(mEmptyCoordinator.getView(), emptyViewParams); + // remove onGlobalLayout listener. + mEmptyView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + }); + mMainView.addView(mEmptyView, emptyViewParams); FrameLayout.LayoutParams listParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); mMainView.addView(mListView.getView(), listParams); + + // Bring to front to make empty view scrollable. + mEmptyView.bringToFront(); } /** Tears down this coordinator. */
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListView.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListView.java index 1aa7fc8..81466d2 100644 --- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListView.java +++ b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListView.java
@@ -48,10 +48,12 @@ private final RecyclerView mView; private final GridLayoutManager mGridLayoutManager; private final UiConfig mUiConfig; + private Runnable mOnConfigurationChangedCallback; /** Creates an instance of a {@link DateOrderedListView} representing {@code model}. */ public DateOrderedListView(Context context, DownloadManagerUiConfig config, - DecoratedListItemModel model, DateOrderedListObserver dateOrderedListObserver) { + DecoratedListItemModel model, DateOrderedListObserver dateOrderedListObserver, + Runnable onConfigurationChangedCallback) { mConfig = config; mModel = model; @@ -79,6 +81,7 @@ mScreenOrientation = newConfig.orientation; mView.invalidateItemDecorations(); + mOnConfigurationChangedCallback.run(); } }; mView.setId(R.id.download_home_recycler_view); @@ -112,6 +115,7 @@ ViewCompat.setPaddingRelative( mView, padding, mView.getPaddingTop(), padding, mView.getPaddingBottom()); }); + mOnConfigurationChangedCallback = onConfigurationChangedCallback; } /** @return The Android {@link View} representing this widget. */
diff --git a/chrome/browser/extensions/api/commands/command_service.cc b/chrome/browser/extensions/api/commands/command_service.cc index f57c3d1..294b6ce 100644 --- a/chrome/browser/extensions/api/commands/command_service.cc +++ b/chrome/browser/extensions/api/commands/command_service.cc
@@ -29,7 +29,6 @@ #include "extensions/browser/extension_function_registry.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_system.h" -#include "extensions/browser/notification_types.h" #include "extensions/common/api/commands/commands_handler.h" #include "extensions/common/command.h" #include "extensions/common/feature_switch.h"
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 1a441b5..0fdfe35 100644 --- a/chrome/browser/extensions/api/developer_private/developer_private_api.cc +++ b/chrome/browser/extensions/api/developer_private/developer_private_api.cc
@@ -90,7 +90,6 @@ #include "extensions/browser/extension_util.h" #include "extensions/browser/file_highlighter.h" #include "extensions/browser/management_policy.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/path_util.h" #include "extensions/browser/permissions_manager.h" #include "extensions/browser/process_manager_factory.h"
diff --git a/chrome/browser/extensions/api/image_writer_private/operation_manager.cc b/chrome/browser/extensions/api/image_writer_private/operation_manager.cc index 614302e4..b0734b9 100644 --- a/chrome/browser/extensions/api/image_writer_private/operation_manager.cc +++ b/chrome/browser/extensions/api/image_writer_private/operation_manager.cc
@@ -25,7 +25,6 @@ #include "extensions/browser/api/extensions_api_client.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_host.h" -#include "extensions/browser/notification_types.h" #include "mojo/public/cpp/bindings/pending_remote.h" #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/extensions/api/management/management_api_browsertest.cc b/chrome/browser/extensions/api/management/management_api_browsertest.cc index 7bbe060..e7877539 100644 --- a/chrome/browser/extensions/api/management/management_api_browsertest.cc +++ b/chrome/browser/extensions/api/management/management_api_browsertest.cc
@@ -32,7 +32,6 @@ #include "extensions/browser/extension_host_test_helper.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_registry.h" -#include "extensions/browser/notification_types.h" #include "extensions/common/extension_builder.h" #include "extensions/common/extension_id.h" #include "extensions/test/extension_test_message_listener.h"
diff --git a/chrome/browser/extensions/app_background_page_apitest.cc b/chrome/browser/extensions/app_background_page_apitest.cc index ae04093..2bbe22b 100644 --- a/chrome/browser/extensions/app_background_page_apitest.cc +++ b/chrome/browser/extensions/app_background_page_apitest.cc
@@ -52,7 +52,7 @@ #endif #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif using extensions::Extension;
diff --git a/chrome/browser/extensions/browsertest_util.cc b/chrome/browser/extensions/browsertest_util.cc index 1c960eab..33e787a 100644 --- a/chrome/browser/extensions/browsertest_util.cc +++ b/chrome/browser/extensions/browsertest_util.cc
@@ -28,7 +28,6 @@ #include "content/public/test/test_utils.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" -#include "extensions/browser/notification_types.h" #include "extensions/common/extension.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h"
diff --git a/chrome/browser/extensions/chrome_extension_test_notification_observer.cc b/chrome/browser/extensions/chrome_extension_test_notification_observer.cc index 5f73e4f..3bbbfbe 100644 --- a/chrome/browser/extensions/chrome_extension_test_notification_observer.cc +++ b/chrome/browser/extensions/chrome_extension_test_notification_observer.cc
@@ -12,9 +12,9 @@ #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "content/public/browser/browser_context.h" +#include "content/public/browser/notification_types.h" #include "content/public/browser/web_contents.h" #include "content/public/test/test_utils.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/process_manager.h" #include "extensions/common/extension.h"
diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.cc b/chrome/browser/extensions/chrome_extensions_browser_client.cc index 1b6ffd6e..d085384 100644 --- a/chrome/browser/extensions/chrome_extensions_browser_client.cc +++ b/chrome/browser/extensions/chrome_extensions_browser_client.cc
@@ -198,17 +198,15 @@ profile->GetPrefs()->GetBoolean(prefs::kDisableExtensions); } -bool ChromeExtensionsBrowserClient::IsValidContext( - content::BrowserContext* context) { +bool ChromeExtensionsBrowserClient::IsValidContext(void* context) { DCHECK(context); if (!g_browser_process) { LOG(ERROR) << "Unexpected null g_browser_process"; NOTREACHED(); return false; } - Profile* profile = static_cast<Profile*>(context); return g_browser_process->profile_manager() && - g_browser_process->profile_manager()->IsValidProfile(profile); + g_browser_process->profile_manager()->IsValidProfile(context); } bool ChromeExtensionsBrowserClient::IsSameContext(
diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.h b/chrome/browser/extensions/chrome_extensions_browser_client.h index c853adce..3d8d1ba 100644 --- a/chrome/browser/extensions/chrome_extensions_browser_client.h +++ b/chrome/browser/extensions/chrome_extensions_browser_client.h
@@ -69,7 +69,7 @@ bool IsShuttingDown() override; bool AreExtensionsDisabled(const base::CommandLine& command_line, content::BrowserContext* context) override; - bool IsValidContext(content::BrowserContext* context) override; + bool IsValidContext(void* context) override; bool IsSameContext(content::BrowserContext* first, content::BrowserContext* second) override; bool HasOffTheRecordContext(content::BrowserContext* context) override;
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc index 0aaadd3..7780b2c 100644 --- a/chrome/browser/extensions/crx_installer.cc +++ b/chrome/browser/extensions/crx_installer.cc
@@ -42,7 +42,6 @@ #include "components/crx_file/crx_verifier.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/notification_service.h" #include "extensions/browser/content_verifier.h" #include "extensions/browser/extension_file_task_runner.h" #include "extensions/browser/extension_prefs.h" @@ -52,7 +51,6 @@ #include "extensions/browser/install/extension_install_ui.h" #include "extensions/browser/install_flag.h" #include "extensions/browser/install_stage.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/policy_check.h" #include "extensions/browser/preload_check_group.h" #include "extensions/browser/requirements_checker.h" @@ -979,12 +977,6 @@ if (!service_weak_.get() || service_weak_->browser_terminating()) return; - content::NotificationService* service = - content::NotificationService::current(); - service->Notify(NOTIFICATION_EXTENSION_INSTALL_ERROR, - content::Source<CrxInstaller>(this), - content::Details<const CrxInstallError>(&error)); - // This isn't really necessary, it is only used because unit tests expect to // see errors get reported via this interface. //
diff --git a/chrome/browser/extensions/crx_installer_browsertest.cc b/chrome/browser/extensions/crx_installer_browsertest.cc index 0f591af..0bd76dbe 100644 --- a/chrome/browser/extensions/crx_installer_browsertest.cc +++ b/chrome/browser/extensions/crx_installer_browsertest.cc
@@ -58,7 +58,6 @@ #include "extensions/browser/install/crx_install_error.h" #include "extensions/browser/install/sandboxed_unpacker_failure_reason.h" #include "extensions/browser/management_policy.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/permissions_manager.h" #include "extensions/browser/renderer_startup_helper.h" #include "extensions/browser/test_extension_registry_observer.h"
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc index 56137ca..60ac3dcc 100644 --- a/chrome/browser/extensions/extension_browsertest.cc +++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -72,7 +72,6 @@ #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/service_worker/service_worker_test_utils.h" #include "extensions/browser/test_extension_registry_observer.h" #include "extensions/browser/uninstall_reason.h"
diff --git a/chrome/browser/extensions/extension_service_test_with_install.cc b/chrome/browser/extensions/extension_service_test_with_install.cc index cb4c919..4d33bfc 100644 --- a/chrome/browser/extensions/extension_service_test_with_install.cc +++ b/chrome/browser/extensions/extension_service_test_with_install.cc
@@ -18,7 +18,6 @@ #include "content/public/browser/notification_service.h" #include "content/public/test/browser_task_environment.h" #include "extensions/browser/extension_creator.h" -#include "extensions/browser/notification_types.h" #include "extensions/common/verifier_formats.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/browser/extensions/installed_loader.cc b/chrome/browser/extensions/installed_loader.cc index 74db1ef..bd56c5f9 100644 --- a/chrome/browser/extensions/installed_loader.cc +++ b/chrome/browser/extensions/installed_loader.cc
@@ -447,7 +447,7 @@ void InstalledLoader::RecordExtensionsMetricsForTesting() { RecordExtensionsMetrics(/*profile=*/extension_service_->profile(), - /*log_user_profile_histograms=*/false); + /*is_user_profile=*/false); } void InstalledLoader::RecordExtensionsIncrementedMetricsForTesting( @@ -456,9 +456,8 @@ } // TODO(crbug.com/1163038): Separate out Webstore/Offstore metrics. -void InstalledLoader::RecordExtensionsMetrics( - Profile* profile, - bool should_record_incremented_metrics) { +void InstalledLoader::RecordExtensionsMetrics(Profile* profile, + bool is_user_profile) { ExtensionManagement* extension_management = ExtensionManagementFactory::GetForBrowserContext(profile); int app_user_count = 0; @@ -490,6 +489,8 @@ int enabled_not_allowlisted_count = 0; int disabled_not_allowlisted_count = 0; + bool should_record_incremented_metrics = is_user_profile; + const ExtensionSet& extensions = extension_registry_->enabled_extensions(); for (ExtensionSet::const_iterator iter = extensions.begin(); iter != extensions.end(); @@ -568,6 +569,48 @@ web_request_count++; } + // 10 is arbitrarily chosen. + static constexpr int kMaxManifestVersion = 10; + // ManifestVersion split by location for items of type + // Manifest::TYPE_EXTENSION. An ungrouped histogram is below, includes all + // extension-y types (such as platform apps and hosted apps), and doesn't + // include unpacked or component locations. + if (extension->is_extension() && is_user_profile) { + const char* location_histogram_name = nullptr; + switch (extension->location()) { + case mojom::ManifestLocation::kInternal: + location_histogram_name = + "Extensions.ManifestVersionByLocation.Internal"; + break; + case mojom::ManifestLocation::kExternalPref: + case mojom::ManifestLocation::kExternalPrefDownload: + case mojom::ManifestLocation::kExternalRegistry: + location_histogram_name = + "Extensions.ManifestVersionByLocation.External"; + break; + case mojom::ManifestLocation::kComponent: + case mojom::ManifestLocation::kExternalComponent: + location_histogram_name = + "Extensions.ManifestVersionByLocation.Component"; + break; + case mojom::ManifestLocation::kExternalPolicy: + case mojom::ManifestLocation::kExternalPolicyDownload: + location_histogram_name = + "Extensions.ManifestVersionByLocation.Policy"; + break; + case mojom::ManifestLocation::kCommandLine: + case mojom::ManifestLocation::kUnpacked: + location_histogram_name = + "Extensions.ManifestVersionByLocation.Unpacked"; + break; + case mojom::ManifestLocation::kInvalidLocation: + NOTREACHED_NORETURN(); + } + base::UmaHistogramExactLinear(location_histogram_name, + extension->manifest_version(), + kMaxManifestVersion); + } + // From now on, don't count component extensions, since they are only // extensions as an implementation detail. Continue to count unpacked // extensions for a few metrics. @@ -600,10 +643,11 @@ UMA_HISTOGRAM_ENUMERATION("Extensions.ManifestVersion", extension->manifest_version(), - 10); // 10 is arbitrarily chosen. + kMaxManifestVersion); if (should_record_incremented_metrics) { UMA_HISTOGRAM_ENUMERATION("Extensions.ManifestVersion2", - extension->manifest_version(), 10); + extension->manifest_version(), + kMaxManifestVersion); } // We might have wanted to count legacy packaged apps here, too, since they
diff --git a/chrome/browser/extensions/installed_loader.h b/chrome/browser/extensions/installed_loader.h index 3a24cc3..867d90d 100644 --- a/chrome/browser/extensions/installed_loader.h +++ b/chrome/browser/extensions/installed_loader.h
@@ -62,11 +62,11 @@ // extension that is already installed. int GetCreationFlags(const ExtensionInfo* info); - // Record metrics related to the loaded extensions. - // `log_user_profile_histograms` being `true` causes profile-specific - // incremented histograms to emit. - void RecordExtensionsMetrics(Profile* profile, - bool log_user_profile_histograms); + // Records metrics related to the loaded extensions. + // `is_user_profile` indicates that this is a profile where users can install + // extensions, specifically profiles that can have non-component extensions + // installed. This causes incremented histograms to emit. + void RecordExtensionsMetrics(Profile* profile, bool is_user_profile); raw_ptr<ExtensionService> extension_service_; raw_ptr<ExtensionRegistry> extension_registry_;
diff --git a/chrome/browser/extensions/load_error_reporter.cc b/chrome/browser/extensions/load_error_reporter.cc index 322d26c..df6feef 100644 --- a/chrome/browser/extensions/load_error_reporter.cc +++ b/chrome/browser/extensions/load_error_reporter.cc
@@ -18,7 +18,6 @@ #include "chrome/browser/ui/simple_message_box.h" #include "chrome/grit/generated_resources.h" #include "content/public/browser/notification_service.h" -#include "extensions/browser/notification_types.h" #include "ui/base/l10n/l10n_util.h" namespace extensions {
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc index 64f1e00..8adbc20e 100644 --- a/chrome/browser/extensions/service_worker_apitest.cc +++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -352,6 +352,9 @@ histogram_tester.ExpectTotalCount( "Extensions.Events.DispatchToAckLongTime.ExtensionServiceWorker2", /*expected_count=*/2); + histogram_tester.ExpectTotalCount( + "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker", + /*expected_count=*/2); // Verify that the recorded values are sane -- that is, that they are less // than the maximum bucket. @@ -363,6 +366,9 @@ histogram_tester.ExpectBucketCount( "Extensions.Events.DispatchToAckLongTime.ExtensionServiceWorker2", /*sample=*/base::Days(1).InMilliseconds(), /*expected_count=*/0); + histogram_tester.ExpectBucketCount( + "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker", + /*sample=*/false, /*expected_count=*/0); } // After browser restarts, this test step ensures that opening a tab fires
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index 1ea0027..acb2637 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -155,6 +155,11 @@ "expiry_milestone": 130 }, { + "name": "almanac-game-migration", + "owners": [ "elitsa@google.com", "cros-apps-foundation-system@google.com" ], + "expiry_milestone": 123 + }, + { "name": "alt-click-and-six-pack-customization", "owners": [ "michaelcheco", "cros-peripherals@google.com" ], "expiry_milestone": 130 @@ -218,7 +223,7 @@ }, { "name": "app-deduplication-service-fondue", - "owners": [ "nikkifang", "melzhang", "chromeos-apps-foundation-team" ], + "owners": [ "tsergeant", "chromeos-apps-foundation-team@google.com" ], "expiry_milestone": 118 }, { @@ -233,7 +238,7 @@ }, { "name": "app-management-app-details", - "owners": [ "jshikaram", "chromeos-apps-foundation-team" ], + "owners": [ "tsergeant", "chromeos-apps-foundation-team@google.com" ], "expiry_milestone": 118 }, { @@ -672,11 +677,6 @@ "expiry_milestone": 120 }, { - "name": "autofill-enforce-delays-in-strike-database", - "owners": [ "siyua"], - "expiry_milestone": 116 - }, - { "name": "autofill-fill-merchant-promo-code-fields", "owners": [ "jsaul@google.com", "payments-autofill-team@google.com" ], "expiry_milestone": 116
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 30024e6..8465b06 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -5201,6 +5201,12 @@ "Shows settings to enable/disable scroll acceleration and to adjust the " "sensitivity for scrolling."; +const char kAlmanacGameMigrationName[] = + "Use Almanac for games in App Discovery Service"; +const char kAlmanacGameMigrationDescription[] = + "Enables App Discovery Service to use the Almanac system for fetching " + "games instead of the App Provisioning Component."; + const char kAltClickAndSixPackCustomizationName[] = "Allow users to customize Alt-Click and 6-pack key remapping.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index d1f59c5c..4242a1a3 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -3012,6 +3012,10 @@ extern const char kAllowScrollSettingsDescription[]; extern const char kAllowTouchpadScrollSettingsName[]; extern const char kAllowTouchpadScrollSettingsDescription[]; + +extern const char kAlmanacGameMigrationName[]; +extern const char kAlmanacGameMigrationDescription[]; + extern const char kAltClickAndSixPackCustomizationName[]; extern const char kAltClickAndSixPackCustomizationDescription[]; extern const char kAlwaysEnableHdcpName[];
diff --git a/chrome/browser/gesturenav/android/java/src/org/chromium/chrome/browser/gesturenav/TabOnBackGestureHandler.java b/chrome/browser/gesturenav/android/java/src/org/chromium/chrome/browser/gesturenav/TabOnBackGestureHandler.java index 29c2e6d..8e7fb03 100644 --- a/chrome/browser/gesturenav/android/java/src/org/chromium/chrome/browser/gesturenav/TabOnBackGestureHandler.java +++ b/chrome/browser/gesturenav/android/java/src/org/chromium/chrome/browser/gesturenav/TabOnBackGestureHandler.java
@@ -28,17 +28,18 @@ private TabOnBackGestureHandler(Tab tab) { mNativePtr = TabOnBackGestureHandlerJni.get().init(tab); } - void onBackStarted(float x, float y, float progress, @BackGestureEventSwipeEdge int edge, + public void onBackStarted(float x, float y, float progress, @BackGestureEventSwipeEdge int edge, boolean forward) { TabOnBackGestureHandlerJni.get().onBackStarted(mNativePtr, x, y, progress, edge, forward); } - void onBackProgressed(float x, float y, float progress, @BackGestureEventSwipeEdge int edge) { + public void onBackProgressed( + float x, float y, float progress, @BackGestureEventSwipeEdge int edge) { TabOnBackGestureHandlerJni.get().onBackProgressed(mNativePtr, x, y, progress, edge); } - void onBackCancelled() { + public void onBackCancelled() { TabOnBackGestureHandlerJni.get().onBackCancelled(mNativePtr); } - void onBackInvoked() { + public void onBackInvoked() { TabOnBackGestureHandlerJni.get().onBackInvoked(mNativePtr); } @Override
diff --git a/chrome/browser/mac/install_from_dmg.mm b/chrome/browser/mac/install_from_dmg.mm index b50b0e99..1c0323f 100644 --- a/chrome/browser/mac/install_from_dmg.mm +++ b/chrome/browser/mac/install_from_dmg.mm
@@ -21,6 +21,7 @@ #include "base/apple/bridging.h" #include "base/apple/bundle_locations.h" +#include "base/apple/mach_logging.h" #include "base/apple/osstatus_logging.h" #include "base/auto_reset.h" #include "base/command_line.h" @@ -29,7 +30,6 @@ #include "base/mac/authorization_util.h" #include "base/mac/foundation_util.h" #include "base/mac/mac_util.h" -#include "base/mac/mach_logging.h" #include "base/mac/scoped_authorizationref.h" #include "base/mac/scoped_cftyperef.h" #include "base/mac/scoped_ioobject.h"
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_factory.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_factory.cc index bace05b..4513551 100644 --- a/chrome/browser/nearby_sharing/nearby_sharing_service_factory.cc +++ b/chrome/browser/nearby_sharing/nearby_sharing_service_factory.cc
@@ -12,6 +12,7 @@ #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "chrome/browser/ash/nearby/nearby_process_manager_factory.h" +#include "chrome/browser/ash/profiles/profile_helper.h" #include "chrome/browser/nearby_sharing/common/nearby_share_features.h" #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h" #include "chrome/browser/nearby_sharing/logging/logging.h" @@ -26,6 +27,7 @@ #include "components/pref_registry/pref_registry_syncable.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_service.h" +#include "components/user_manager/user_manager.h" #include "content/public/browser/browser_context.h" namespace { @@ -49,22 +51,32 @@ // static bool NearbySharingServiceFactory::IsNearbyShareSupportedForBrowserContext( content::BrowserContext* context) { - if (IsSupportedTesting().has_value()) + if (IsSupportedTesting().has_value()) { return *IsSupportedTesting(); + } - if (!base::FeatureList::IsEnabled(features::kNearbySharing)) - return false; - - Profile* profile = Profile::FromBrowserContext(context); - if (!profile) - return false; - - if (!ash::nearby::NearbyProcessManagerFactory::CanBeLaunchedForProfile( - profile)) { + if (!base::FeatureList::IsEnabled(features::kNearbySharing)) { return false; } - return true; + Profile* profile = Profile::FromBrowserContext(context); + if (!profile) { + return false; + } + + // Guest/incognito/signin profiles cannot use Nearby Share. + if (ash::ProfileHelper::IsSigninProfile(profile) || + profile->IsOffTheRecord()) { + return false; + } + + // Likewise, kiosk users are ineligible. + if (user_manager::UserManager::Get()->IsLoggedInAsAnyKioskApp()) { + return false; + } + + // Nearby Share is not supported for secondary profiles. + return ash::ProfileHelper::IsPrimaryProfile(profile); } // static
diff --git a/chrome/browser/payments/payment_handler_enforce_full_delegation_browsertest.cc b/chrome/browser/payments/payment_handler_enforce_full_delegation_browsertest.cc index 400d5f3..39b8a1a 100644 --- a/chrome/browser/payments/payment_handler_enforce_full_delegation_browsertest.cc +++ b/chrome/browser/payments/payment_handler_enforce_full_delegation_browsertest.cc
@@ -81,8 +81,9 @@ } } -// crbug.com/1468262: Flaky test. -#if BUILDFLAG(IS_ANDROID) +// crbug.com/1468262: Flaky test on Android. +// crbug.com/1473404: Flaky test on ChromeOS. +#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS) #define MAYBE_WhenEnabled_ShowPaymentSheet_WhenDisabled_Reject \ DISABLED_WhenEnabled_ShowPaymentSheet_WhenDisabled_Reject #else
diff --git a/chrome/browser/policy/extension_policy_browsertest.cc b/chrome/browser/policy/extension_policy_browsertest.cc index 3318707..6e7e0522 100644 --- a/chrome/browser/policy/extension_policy_browsertest.cc +++ b/chrome/browser/policy/extension_policy_browsertest.cc
@@ -72,7 +72,6 @@ #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/extensions_browser_client.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/scoped_ignore_content_verifier_for_test.h" #include "extensions/browser/test_extension_registry_observer.h" #include "extensions/browser/updater/extension_cache_fake.h"
diff --git a/chrome/browser/printing/system_access_process_print_browsertest.cc b/chrome/browser/printing/system_access_process_print_browsertest.cc index 8ca4bf1e..c41f5bf 100644 --- a/chrome/browser/printing/system_access_process_print_browsertest.cc +++ b/chrome/browser/printing/system_access_process_print_browsertest.cc
@@ -575,8 +575,27 @@ .shadowRoot.querySelector('print-preview-button-strip') .shadowRoot.querySelector('.action-button'); button.click();)"; - ASSERT_TRUE(content::ExecJs(preview_dialog, kScript)); + auto result = content::ExecJs(preview_dialog, kScript); + // TODO(crbug.com/1472464): Update once it is known if the assertion + // should not happen if the failure is just because the renderer + // terminated. + // If the renderer terminates, it will return a failing result. It has + // been observed in other tests that sometimes the renderer terminates + // and the test was successful; all the needed callbacks happened before + // ExecJs() returned. + // Add a warning for the logs to help with debugging, and then only do + // the assert check after having done the wait. + // If the renderer terminated but the printing was all successful, then + // `WaitUntilCallbackReceived()` should return successfully, and any crash + // logs should show the assert. Otherwise the crashes for this bug should + // change to become the test timeouts. + if (!result) { + LOG(ERROR) << "ExecJs() failed; if reason is because the renderer " + "terminated, it is possibly okay?"; + LOG(ERROR) << result.message(); + } WaitUntilCallbackReceived(); + ASSERT_TRUE(result); } void AdjustMediaAfterPreviewIsReadyAndLoaded() {
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerCoordinator.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerCoordinator.java index 028be7b7..a3bc4fb 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerCoordinator.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerCoordinator.java
@@ -6,8 +6,12 @@ import android.content.Context; +import org.chromium.base.ObserverList; import org.chromium.chrome.browser.readaloud.PlayerState; import org.chromium.chrome.modules.readaloud.ExpandedPlayer; +import org.chromium.chrome.modules.readaloud.ExpandedPlayer.Observer; +import org.chromium.chrome.modules.readaloud.Playback; +import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent; import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyModel; @@ -16,6 +20,7 @@ public class ExpandedPlayerCoordinator implements ExpandedPlayer { private final Context mContext; private final BottomSheetController mBottomSheetController; + private final ObserverList<Observer> mObserverList; private ExpandedPlayerSheetContent mSheetContent; private PropertyModel mModel; private PropertyModelChangeProcessor<PropertyModel, ExpandedPlayerSheetContent, PropertyKey> @@ -25,10 +30,22 @@ public ExpandedPlayerCoordinator(Context context, BottomSheetController bottomSheetController) { mContext = context; mBottomSheetController = bottomSheetController; + mObserverList = new ObserverList<Observer>(); } @Override - public void show() { + public void addObserver(Observer observer) { + mObserverList.addObserver(observer); + } + + @Override + public void removeObserver(Observer observer) { + mObserverList.removeObserver(observer); + } + + @Override + public void show(Playback playback) { + assert playback != null; if (mSheetContent == null) { mSheetContent = new ExpandedPlayerSheetContent(mContext, mBottomSheetController); mModel = new PropertyModel.Builder(ExpandedPlayerProperties.ALL_KEYS) @@ -36,8 +53,34 @@ .build(); mModelChangeProcessor = PropertyModelChangeProcessor.create( mModel, mSheetContent, ExpandedPlayerViewBinder::bind); - mMediator = new ExpandedPlayerMediator(mBottomSheetController, mModel); + mMediator = new ExpandedPlayerMediator(mBottomSheetController, mModel, new Observer() { + @Override + public void onCloseClicked() { + for (Observer observer : mObserverList) { + observer.onCloseClicked(); + } + } + }); } - mMediator.show(); + mMediator.show(playback); + } + + @Override + public void dismiss() { + if (mMediator != null) { + mMediator.dismiss(); + } + } + + @Override + public @PlayerState int getState() { + if (mMediator == null) { + return PlayerState.GONE; + } + return mMediator.getState(); + } + + BottomSheetContent getSheetContentForTesting() { + return mSheetContent; } }
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerCoordinatorUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerCoordinatorUnitTest.java index dbfa038..a69af09 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerCoordinatorUnitTest.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerCoordinatorUnitTest.java
@@ -4,6 +4,7 @@ package org.chromium.chrome.browser.readaloud.expandedplayer; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.eq; @@ -22,6 +23,8 @@ import org.robolectric.annotation.Config; import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.chrome.modules.readaloud.ExpandedPlayer.Observer; +import org.chromium.chrome.modules.readaloud.Playback; import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; /** Unit tests for {@link ExpandedPlayerCoordinator}. */ @@ -30,6 +33,10 @@ public class ExpandedPlayerCoordinatorUnitTest { @Mock private BottomSheetController mBottomSheetController; + @Mock + private Playback mPlayback; + @Mock + private Observer mObserver; private ExpandedPlayerCoordinator mCoordinator; @@ -42,12 +49,33 @@ @Test public void testShowInflatesViewOnce() { - mCoordinator.show(); + mCoordinator.show(mPlayback); verify(mBottomSheetController, times(1)).requestShowContent(any(), eq(true)); // Second show() shouldn't inflate the stub again. reset(mBottomSheetController); - mCoordinator.show(); + mCoordinator.show(mPlayback); verify(mBottomSheetController, never()).requestShowContent(any(), anyBoolean()); } + + @Test + public void testDismiss() { + mCoordinator.show(mPlayback); + mCoordinator.dismiss(); + verify(mBottomSheetController) + .hideContent(eq(mCoordinator.getSheetContentForTesting()), eq(true)); + } + + @Test + public void testObserveClickClose() { + mCoordinator.addObserver(mObserver); + mCoordinator.show(mPlayback); + + assertTrue(mCoordinator.getSheetContentForTesting() + .getContentView() + .findViewById(R.id.readaloud_expanded_player_close_button) + .performClick()); + + verify(mObserver, times(1)).onCloseClicked(); + } }
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerMediator.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerMediator.java index 08640bc..12b072f0 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerMediator.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerMediator.java
@@ -5,6 +5,8 @@ package org.chromium.chrome.browser.readaloud.expandedplayer; import org.chromium.chrome.browser.readaloud.PlayerState; +import org.chromium.chrome.modules.readaloud.ExpandedPlayer.Observer; +import org.chromium.chrome.modules.readaloud.Playback; import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason; import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver; @@ -14,19 +16,24 @@ public class ExpandedPlayerMediator extends EmptyBottomSheetObserver { private final BottomSheetController mBottomSheetController; private final PropertyModel mModel; + private final Observer mObserver; public ExpandedPlayerMediator( - BottomSheetController bottomSheetController, PropertyModel model) { + BottomSheetController bottomSheetController, PropertyModel model, Observer observer) { mBottomSheetController = bottomSheetController; mBottomSheetController.addObserver(this); mModel = model; + mObserver = observer; + mModel.set(ExpandedPlayerProperties.ON_CLOSE_CLICK_KEY, + (view) -> { mObserver.onCloseClicked(); }); } public void destroy() { mBottomSheetController.removeObserver(this); } - public void show() { + public void show(Playback playback) { + // TODO use playback @PlayerState int state = getState(); if (state == PlayerState.SHOWING || state == PlayerState.VISIBLE) {
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerMediatorUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerMediatorUnitTest.java index b096517..3c89994 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerMediatorUnitTest.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerMediatorUnitTest.java
@@ -22,6 +22,8 @@ import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.chrome.browser.readaloud.PlayerState; +import org.chromium.chrome.modules.readaloud.ExpandedPlayer; +import org.chromium.chrome.modules.readaloud.Playback; import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; import org.chromium.ui.modelutil.PropertyModel; @@ -31,6 +33,10 @@ public class ExpandedPlayerMediatorUnitTest { @Mock private BottomSheetController mBottomSheetController; + @Mock + private ExpandedPlayer.Observer mObserver; + @Mock + private Playback mPlayback; private PropertyModel mModel; private ExpandedPlayerMediator mMediator; @@ -42,7 +48,7 @@ .with(ExpandedPlayerProperties.STATE_KEY, PlayerState.GONE) .build()); - mMediator = new ExpandedPlayerMediator(mBottomSheetController, mModel); + mMediator = new ExpandedPlayerMediator(mBottomSheetController, mModel, mObserver); } @Test @@ -59,7 +65,7 @@ @Test public void testShow() { - mMediator.show(); + mMediator.show(mPlayback); assertEquals(PlayerState.SHOWING, mMediator.getState()); } @@ -67,13 +73,13 @@ public void testShowAlreadyShowing() { mModel.set(ExpandedPlayerProperties.STATE_KEY, PlayerState.SHOWING); reset(mModel); - mMediator.show(); + mMediator.show(mPlayback); assertEquals(PlayerState.SHOWING, mMediator.getState()); verify(mModel, never()).set(any(), any()); mModel.set(ExpandedPlayerProperties.STATE_KEY, PlayerState.VISIBLE); reset(mModel); - mMediator.show(); + mMediator.show(mPlayback); assertEquals(PlayerState.VISIBLE, mMediator.getState()); verify(mModel, never()).set(any(), any()); } @@ -95,7 +101,7 @@ @Test public void testDismiss() { - mMediator.show(); + mMediator.show(mPlayback); mMediator.dismiss(); assertEquals(PlayerState.HIDING, mMediator.getState()); }
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerProperties.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerProperties.java index 5c7edf9..9e17e704 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerProperties.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerProperties.java
@@ -4,6 +4,8 @@ package org.chromium.chrome.browser.readaloud.expandedplayer; +import android.view.View; + import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey; @@ -13,5 +15,7 @@ new WritableObjectPropertyKey<>(); public static final WritableObjectPropertyKey<Float> SPEED_KEY = new WritableObjectPropertyKey<>(); - public static final PropertyKey[] ALL_KEYS = {STATE_KEY, SPEED_KEY}; + public static final WritableObjectPropertyKey<View.OnClickListener> ON_CLOSE_CLICK_KEY = + new WritableObjectPropertyKey<>(); + public static final PropertyKey[] ALL_KEYS = {STATE_KEY, SPEED_KEY, ON_CLOSE_CLICK_KEY}; }
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerSheetContent.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerSheetContent.java index 0dba9e3a..68fe74ed 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerSheetContent.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerSheetContent.java
@@ -59,6 +59,11 @@ mBottomSheetController.hideContent(this, /*animate=*/true); } + void setCloseButtonHandler(View.OnClickListener onClick) { + mContentView.findViewById(R.id.readaloud_expanded_player_close_button) + .setOnClickListener(onClick); + } + @SuppressWarnings({"SetTextI18n", "DefaultLocale"}) public void setSpeed(float speed) { TextView speedButton = (TextView) mContentView.findViewById(R.id.readaloud_playback_speed);
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerViewBinder.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerViewBinder.java index 46b0d20..dadabf8 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerViewBinder.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/expandedplayer/ExpandedPlayerViewBinder.java
@@ -26,6 +26,8 @@ } } else if (key == ExpandedPlayerProperties.SPEED_KEY) { content.setSpeed(model.get(ExpandedPlayerProperties.SPEED_KEY)); + } else if (key == ExpandedPlayerProperties.ON_CLOSE_CLICK_KEY) { + content.setCloseButtonHandler(model.get(ExpandedPlayerProperties.ON_CLOSE_CLICK_KEY)); } } }
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn index 70891406..38603a8e 100644 --- a/chrome/browser/resources/BUILD.gn +++ b/chrome/browser/resources/BUILD.gn
@@ -32,6 +32,7 @@ "feed:resources", "feedback:resources", "gaia_auth_host:resources", + "hats:resources", "history:resources", "identity_internals:resources", "inline_login:resources",
diff --git a/chrome/browser/resources/ash/settings/device_page/display.html b/chrome/browser/resources/ash/settings/device_page/display.html index fedbc54..18becf40 100644 --- a/chrome/browser/resources/ash/settings/device_page/display.html +++ b/chrome/browser/resources/ash/settings/device_page/display.html
@@ -158,12 +158,12 @@ <!-- Display zoom selection slider --> <div class="settings-box indented two-line first"> <div class="start text-area layout vertical"> - <div id="displayZoomTitle" aria-hidden="true"> - $i18n{displayZoomTitle} + <div id="displayZoomLabel" aria-hidden="true"> + $i18n{displayZoomLabel} </div> - <div id="displayZoomSublabel" class="secondary self-start" + <div id="displayZoomDescription" class="secondary self-start" aria-hidden="true"> - $i18n{displayZoomSublabel} + $i18n{displayZoomDescription} </div> <div id="logicalResolutionText" class="secondary self-start" hidden$="[[!logicalResolutionText_]]" aria-hidden="true"> @@ -174,12 +174,12 @@ selectedDisplay, prefs.cros.device_display_resolution)]]"> <cr-policy-pref-indicator pref="[[prefs.cros.device_display_resolution]]" - icon-aria-label="$i18n{displayZoomTitle}"> + icon-aria-label="$i18n{displayZoomLabel}"> </cr-policy-pref-indicator> </template> <settings-slider id="displaySizeSlider" ticks="[[zoomValues_]]" pref="{{selectedZoomPref_}}" - label-aria="$i18n{displayZoomTitle}" + label-aria="$i18n{displayZoomLabel}" label-min="$i18n{displaySizeSliderMinLabel}" label-max="$i18n{displaySizeSliderMaxLabel}" disabled="[[isDisplayScaleMandatory_(
diff --git a/chrome/browser/resources/ash/settings/device_page/display_overscan_dialog.html b/chrome/browser/resources/ash/settings/device_page/display_overscan_dialog.html index d49af617..a66750e 100644 --- a/chrome/browser/resources/ash/settings/device_page/display_overscan_dialog.html +++ b/chrome/browser/resources/ash/settings/device_page/display_overscan_dialog.html
@@ -59,7 +59,9 @@ <div slot="title">$i18n{displayOverscanPageTitle}</div> <div slot="body"> <div class="subtitle" >$i18n{displayOverscanSubtitle}</div> - <div class="instructions" >$i18n{displayOverscanInstructions}</div> + <div class="instructions" hidden="[[isRevampWayfindingEnabled_]]"> + $i18n{displayOverscanInstructions} + </div> <div class="details layout horizontal around-justified self-stretch"> <div class="layout vertical center"> <div class="layout horizontal">
diff --git a/chrome/browser/resources/ash/settings/device_page/display_overscan_dialog.ts b/chrome/browser/resources/ash/settings/device_page/display_overscan_dialog.ts index 5c10f21..2b835520 100644 --- a/chrome/browser/resources/ash/settings/device_page/display_overscan_dialog.ts +++ b/chrome/browser/resources/ash/settings/device_page/display_overscan_dialog.ts
@@ -19,6 +19,8 @@ import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {isRevampWayfindingEnabled} from '../common/load_time_booleans.js'; + import {getDisplayApi} from './device_page_browser_proxy.js'; import {getTemplate} from './display_overscan_dialog.html.js'; @@ -52,11 +54,19 @@ Set to true once changes are saved to avoid a reset/cancel on close. */ committed_: Boolean, + + isRevampWayfindingEnabled_: { + type: Boolean, + value: () => { + return isRevampWayfindingEnabled(); + }, + }, }; } displayId: string; private committed_: boolean; + private isRevampWayfindingEnabled_: boolean; private keyHandler_: (event: KeyboardEvent) => void; constructor() {
diff --git a/chrome/browser/resources/ash/settings/os_printing_page/cups_printers_entry_manager.ts b/chrome/browser/resources/ash/settings/os_printing_page/cups_printers_entry_manager.ts index 99e47b49..7b5be9ca 100644 --- a/chrome/browser/resources/ash/settings/os_printing_page/cups_printers_entry_manager.ts +++ b/chrome/browser/resources/ash/settings/os_printing_page/cups_printers_entry_manager.ts
@@ -42,6 +42,10 @@ instance = obj; } + static resetForTesting(): void { + instance = null; + } + printServerPrinters: PrinterListEntry[]; private enterprisePrinters_: PrinterListEntry[];
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn index 1f7bbb2..7bb25bb 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -190,7 +190,6 @@ deps = [ ":chromevox_background_script", ":chromevox_copied_files", - ":chromevox_learn_mode_script", ":chromevox_options_script", ":chromevox_panel_script", ":chromevox_phonetic_dictionaries_js", @@ -201,7 +200,6 @@ } chromevox_background_script_loader_file = "background/loader.js" -chromevox_learn_mode_loader_file = "learn_mode/kbexplorer_loader.js" chromevox_options_script_loader_file = "options/options_loader.js" chromevox_panel_script_loader_file = "panel/panel_loader.js" @@ -303,10 +301,6 @@ } # TODO once these loaders are all empty, we will need to re-work the entry points. -compress_js("chromevox_learn_mode_script") { - sources = [ chromevox_learn_mode_loader_file ] - output_file = "$chromevox_out_dir/chromeVoxKbExplorerScript.js" -} compress_js("chromevox_options_script") { sources = [ chromevox_options_script_loader_file ]
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/injected_script_loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/injected_script_loader.js index 9d45e936..6ec0921 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/injected_script_loader.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/injected_script_loader.js
@@ -7,80 +7,121 @@ */ export class InjectedScriptLoader { + /** @private */ + constructor() { + /** @private {!Object<string,string>} */ + this.code_ = {}; + /** @private {!Object<function()>} */ + this.resolveXhr_ = {}; + } + /** * Inject the content scripts into already existing tabs. - * @param {!Array<!Tab>} tabs The tab where ChromeVox scripts should be + * @param {!Array<!Tab>} tabs The tabs where ChromeVox scripts should be * injected. */ static async injectContentScript(tabs) { - const listOfFiles = - chrome.runtime.getManifest()['content_scripts'][0]['js']; - - const loader = new InjectedScriptLoader(); - const code = - await new Promise(resolve => loader.fetchCode_(listOfFiles, resolve)); - for (const tab of tabs) { - // Inject the ChromeVox content script code into the tab. - listOfFiles.forEach(file => loader.execute_(code[file], tab)); + if (!InjectedScriptLoader.instance) { + InjectedScriptLoader.instance = new InjectedScriptLoader(); } + await InjectedScriptLoader.instance.fetchCode_(contentScriptFiles); + await InjectedScriptLoader.instance.executeCodeInAllTabs_(tabs); } /** * Loads a dictionary of file contents for Javascript files. * @param {Array<string>} files A list of file names. - * @param {function(Object<string,string>)} done A function called when all - * the files have been loaded. Called with the code map as the first - * parameter. * @private */ - fetchCode_(files, done) { - const code = {}; - let waiting = files.length; - const startTime = new Date(); - const loadScriptAsCode = function(src) { - // Load the script by fetching its source and running 'eval' on it - // directly, with a magic comment that makes Chrome treat it like it - // loaded normally. Wait until it's fetched before loading the - // next script. - const xhr = new XMLHttpRequest(); - const url = chrome.extension.getURL(src) + '?' + new Date().getTime(); - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - let scriptText = xhr.responseText; - // Add a magic comment to the bottom of the file so that - // Chrome knows the name of the script in the JavaScript debugger. - const debugSrc = src.replace('closure/../', ''); - // The 'chromevox' id is only used in the DevTools instead of a long - // extension id. - scriptText += '\n//# sourceURL= chrome-extension://chromevox/' + - debugSrc + '\n'; - code[src] = scriptText; - waiting--; - if (waiting === 0) { - done(code); - } - } - }; - xhr.open('GET', url); - xhr.send(null); - }; - - files.forEach(file => loadScriptAsCode(file)); + async fetchCode_(files) { + return Promise.all(files.map(file => this.loadScriptAsCode_(file))); } /** - * A helper function which executes code. - * @param {string} code The code to execute. - * @return {!Promise} + * @param {string} fileName + * @private + */ + async loadScriptAsCode_(fileName) { + if (this.code_[fileName]) { + return this.code_[fileName]; + } + + const loaded = new Promise(resolve => this.resolveXhr_[fileName] = resolve); + // Load the script by fetching its source and running 'eval' on it + // directly, with a magic comment that makes Chrome treat it like it + // loaded normally. Wait until it's fetched before loading the + // next script. + const xhr = new XMLHttpRequest(); + xhr.onreadystatechange = () => this.xhrMaybeReady_(xhr, fileName); + const url = chrome.extension.getURL(fileName) + '?' + new Date().getTime(); + xhr.open('GET', url); + xhr.send(null); + return loaded; + } + + /** + * @param {XMLHttpRequest} xhr + * @param {string} fileName + * @private + */ + xhrMaybeReady_(xhr, fileName) { + if (xhr.readyState === 4) { + let scriptText = xhr.responseText; + // Add a magic comment to the bottom of the file so that + // Chrome knows the name of the script in the JavaScript debugger. + const debugSrc = fileName.replace('closure/../', ''); + // The 'chromevox' id is only used in the DevTools instead of a long + // extension id. + scriptText += + '\n//# sourceURL= chrome-extension://chromevox/' + debugSrc + '\n'; + this.code_[fileName] = scriptText; + this.markAsLoaded_(fileName); + } + } + + /** + * @param {string} fileName + * @private + */ + markAsLoaded_(fileName) { + if (!this.resolveXhr_[fileName]) { + return; + } + const callback = this.resolveXhr_[fileName]; + delete this.resolveXhr_[fileName]; + callback(); + } + + /** + * @param {!Array<!Tab>} tabs + * @private + */ + async executeCodeInAllTabs_(tabs) { + for (const tab of tabs) { + // Inject the ChromeVox content script code into the tab. + await Promise.all(this.code_.map(script => this.execute_(script, tab))); + } + } + + /** + * @param {string} code + * @param {!Tab} tab * @private */ async execute_(code, tab) { await new Promise( resolve => chrome.tabs.executeScript( - tab.id, {code, 'allFrames': true}, resolve)); - if (!chrome.extension.lastError) { - return; + tab.id, {code, allFrames: true}, resolve)); + if (chrome.extension.lastError) { + console.error('Could not inject into tab', tab); } - console.error('Could not inject into tab', tab); } } + +/** @type {InjectedScriptLoader} */ +InjectedScriptLoader.instance; + +// Local to module. + +const contentScriptFiles = + chrome.runtime.getManifest()['content_scripts'][0]['js'];
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/kbexplorer_loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/kbexplorer_loader.js deleted file mode 100644 index d88b2c5..0000000 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/kbexplorer_loader.js +++ /dev/null
@@ -1,8 +0,0 @@ -// Copyright 2014 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/** - * @fileoverview This file is empty, but the closure compiler requires an entry - * point. As such this file is that entry point. - */
diff --git a/chrome/browser/resources/chromeos/status_area_internals/BUILD.gn b/chrome/browser/resources/chromeos/status_area_internals/BUILD.gn index cfdb77c4..29550e5 100644 --- a/chrome/browser/resources/chromeos/status_area_internals/BUILD.gn +++ b/chrome/browser/resources/chromeos/status_area_internals/BUILD.gn
@@ -8,7 +8,11 @@ grd_prefix = "status_area_internals" static_files = [ "main.html" ] - web_component_files = [ "status_area_internals.ts" ] + web_component_files = [ + "privacy_indicator_app.ts", + "privacy_indicator_app_manager.ts", + "status_area_internals.ts", + ] ts_definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ] ts_deps = [
diff --git a/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app.html b/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app.html new file mode 100644 index 0000000..8f282ec --- /dev/null +++ b/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app.html
@@ -0,0 +1,53 @@ +<!-- Copyright 2023 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. --> + +<style> + :host { + display: flex; + flex: 1 0 100%; + padding: 10px; + width: 100%; + align-items: center; + } + + #appContainer { + display: flex; + flex: 1 0 100%; + background-color: lightgrey; + } + + cr-input { + padding: 5px; + } + + cr-button { + padding: 25px; + background-color: royalblue; + color: white; + } + + .padded-text { + margin-inline: 5px; + } +</style> + +<div id="appContainer"> + <cr-input label="App Name" aria-label="app-name" value="{{name}}"> + </cr-input> + + <cr-toggle aria-label="use-camera" checked="{{useCamera}}"></cr-toggle> + <span aria-hidden="true" class="padded-text"> + Use camera + </span> + + <cr-toggle aria-label="use-microphone" checked="{{useMicrophone}}"> + </cr-toggle> + <span aria-hidden="true" class="padded-text"> + Use microphone + </span> + + <cr-button on-click="onTriggerPrivacyIndicators_"> + <span class="emphasize">Add/Update App</span> + </cr-button> +</div> \ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app.ts b/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app.ts new file mode 100644 index 0000000..94c79d9 --- /dev/null +++ b/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app.ts
@@ -0,0 +1,72 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'chrome://resources/cr_elements/cr_button/cr_button.js'; +import 'chrome://resources/cr_elements/cr_input/cr_input.js'; +import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js'; + +import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js'; +import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {getTemplate} from './privacy_indicator_app.html.js'; + +/** + * @fileoverview + * 'privacy-indicator-app' defines the UI for the privacy indicator app + * in the status area test page. + */ + +export class PrivacyIndicatorAppElement extends PolymerElement { + static get is() { + return 'privacy-indicator-app'; + } + + static get template() { + return getTemplate(); + } + + static get properties(): PolymerElementProperties { + return { + id: { + type: String, + value: '', + }, + name: { + type: String, + value: '', + }, + useCamera: { + type: Boolean, + value: false, + }, + useMicrophone: { + type: Boolean, + value: false, + }, + }; + } + + private appid: string; + private name: string; + private useCamera: boolean; + private useMicrophone: boolean; + + private onTriggerPrivacyIndicators_(e: Event) { + e.stopPropagation(); + + if (!this.appid || !this.name) { + return; + } + + chrome.send('triggerPrivacyIndicators', [ + this.appid, + this.name, + this.useCamera, + this.useMicrophone, + ]); + } +} + +customElements.define( + PrivacyIndicatorAppElement.is, PrivacyIndicatorAppElement);
diff --git a/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app_manager.html b/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app_manager.html new file mode 100644 index 0000000..1292edb --- /dev/null +++ b/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app_manager.html
@@ -0,0 +1,30 @@ +<!-- Copyright 2023 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. --> + +<style> + :host { + display: flex; + flex: 1 0 100%; + } + + #listContainer { + flex: 3; + height: 40vh; + } +</style> + +<div class="column"> + <cr-button on-click="onAddPrivacyIndicatorsApp_"> + Add Privacy Indicators App + </cr-button> +</div> + +<div class="column" id="listContainer"> + <template id="appListContainer" is="dom-repeat" + items="{{appList}}"> + <privacy-indicator-app appid="{{item}}" + on-remove-app="onRemoveApp_"> + </privacy-indicator-app> + </template> +</div> \ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app_manager.ts b/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app_manager.ts new file mode 100644 index 0000000..9435377 --- /dev/null +++ b/chrome/browser/resources/chromeos/status_area_internals/privacy_indicator_app_manager.ts
@@ -0,0 +1,57 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import './privacy_indicator_app.js'; + +import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js'; +import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {getTemplate} from './privacy_indicator_app_manager.html.js'; + +const testAppIdPrefix = 'chromeos-status-area-test-app'; + +/** + * @fileoverview + * 'privacy-indicator-app-manager' defines the UI for the Privacy + * Indicators app management section of the test page. + */ + +export class PrivacyIndicatorAppManagerElement extends PolymerElement { + static get is() { + return 'privacy-indicator-app-manager'; + } + + static get template() { + return getTemplate(); + } + + static get properties(): PolymerElementProperties { + return { + idLatest: { + type: Number, + value: 0, + }, + appList: { + type: Array, + value: [], + }, + }; + } + + private idLatest: number; + private appList: string[]; + + private onAddPrivacyIndicatorsApp_(e: Event) { + e.stopPropagation(); + + this.appList.push(testAppIdPrefix + this.idLatest); + this.idLatest++; + + // Force `appList` to update in the UI. + this.appList = this.appList.slice(); + } +} + +customElements.define( + PrivacyIndicatorAppManagerElement.is, PrivacyIndicatorAppManagerElement);
diff --git a/chrome/browser/resources/chromeos/status_area_internals/status_area_internals.html b/chrome/browser/resources/chromeos/status_area_internals/status_area_internals.html index cf6620be..4421fd4 100644 --- a/chrome/browser/resources/chromeos/status_area_internals/status_area_internals.html +++ b/chrome/browser/resources/chromeos/status_area_internals/status_area_internals.html
@@ -85,5 +85,8 @@ </span> </div> </li> + + <h2>Privacy Indicators</h2> + <privacy-indicator-app-manager></privacy-indicator-app-manager> </ol> </div> \ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/status_area_internals/status_area_internals.ts b/chrome/browser/resources/chromeos/status_area_internals/status_area_internals.ts index baa461e..e25cdef6 100644 --- a/chrome/browser/resources/chromeos/status_area_internals/status_area_internals.ts +++ b/chrome/browser/resources/chromeos/status_area_internals/status_area_internals.ts
@@ -3,6 +3,7 @@ // found in the LICENSE file. import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js'; +import './privacy_indicator_app_manager.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/engagement/BUILD.gn b/chrome/browser/resources/engagement/BUILD.gn index 29f55169..2e8428f 100644 --- a/chrome/browser/resources/engagement/BUILD.gn +++ b/chrome/browser/resources/engagement/BUILD.gn
@@ -7,7 +7,8 @@ build_webui("build") { grd_prefix = "engagement" static_files = [ "site_engagement.html" ] - non_web_component_files = [ "site_engagement.ts" ] + web_component_files = [ "app.ts" ] + html_to_wrapper_template = "native" mojo_files_deps = [ "//components/site_engagement/core/mojom:mojo_bindings_ts__generator" ]
diff --git a/chrome/browser/resources/engagement/app.html b/chrome/browser/resources/engagement/app.html new file mode 100644 index 0000000..8133f93 --- /dev/null +++ b/chrome/browser/resources/engagement/app.html
@@ -0,0 +1,107 @@ +<style> + :host { + --color-row-hover: rgb(255, 255, 187); + --color-engagement-bar: black; + } + + @media (prefers-color-scheme: dark) { + :host { + --color-row-hover: rgb(3, 220, 176); + --color-engagement-bar: white; + } + } + + table { + border-collapse: collapse; + } + + table td, + table th { + border: 1px solid #777; + padding-left: 4px; + padding-right: 4px; + } + + table th { + background: rgb(224, 236, 255); + color: black; + cursor: pointer; + padding-bottom: 4px; + padding-right: 16px; + padding-top: 4px; + white-space: nowrap; + } + + .engagement-bar { + background-color: var(--color-engagement-bar); + height: 2px; + } + + .engagement-bar-cell { + border: none; + } + + .origin-cell { + background-color: rgba(230, 230, 230, 0.5); + min-width: 500px; + } + + .base-score-cell, + .bonus-score-cell, + .total-score-cell { + background-color: rgba(230, 230, 230, 0.5); + text-align: right; + } + + .base-score-input { + border: 1px solid #ccc; + border-radius: 2px; + text-align: right; + width: 70px; + } + + .base-score-input:focus { + border: 1px solid rgb(143, 185, 252); + box-shadow: 0 0 2px rgb(113, 158, 206); + outline: none; + } + + .add-origin-button { + width: 10em; + } + + table tr:hover { + background: var(--color-row-hover); + } + + th.sort-column::after { + content: 'â–²'; + position: absolute; + } + + th[sort-reverse].sort-column::after { + content: 'â–¼'; + position: absolute; + } +</style> +<h1>Site Engagement</h1> +<table> + <thead> + <tr id="engagement-table-header"> + <th sort-key="origin"> + Origin + </th> + <th sort-key="baseScore" sort-reverse> + Base + </th> + <th sort-key="bonusScore" sort-reverse> + Bonus + </th> + <th sort-key="totalScore" class="sort-column" sort-reverse> + Total + </th> + </tr> + </thead> + <tbody id="engagement-table-body"> + </tbody> +</table>
diff --git a/chrome/browser/resources/engagement/app.ts b/chrome/browser/resources/engagement/app.ts new file mode 100644 index 0000000..0b04269 --- /dev/null +++ b/chrome/browser/resources/engagement/app.ts
@@ -0,0 +1,297 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js'; +import {CustomElement} from 'chrome://resources/js/custom_element.js'; +import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js'; +import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js'; + +import {getTemplate} from './app.html.js'; +import {SiteEngagementDetails, SiteEngagementDetailsProvider, SiteEngagementDetailsProviderInterface} from './site_engagement_details.mojom-webui.js'; + +/** + * Rounds the supplied value to two decimal places of accuracy. + */ +function roundScore(score: number): number { + return Number(Math.round(score * 100) / 100); +} + +/** + * Compares two SiteEngagementDetails objects based on |sortKey|. + * @param sortKey The name of the property to sort by. + * @return A negative number if |a| should be ordered before |b|, a + * positive number otherwise. + */ +function compareTableItem( + sortKey: string, a: {[k: string]: any}, b: {[k: string]: any}): number { + const val1 = a[sortKey]; + const val2 = b[sortKey]; + + // Compare the hosts of the origin ignoring schemes. + if (sortKey === 'origin') { + return new URL(val1.url).host > new URL(val2.url).host ? 1 : -1; + } + + if (sortKey === 'baseScore' || sortKey === 'bonusScore' || + sortKey === 'totalScore') { + return val1 - val2; + } + + assertNotReached('Unsupported sort key: ' + sortKey); +} + + +export class SiteEngagementAppElement extends CustomElement { + static get is() { + return 'site-engagement-app'; + } + + static override get template() { + return getTemplate(); + } + + private engagementTableBody: HTMLElement|null = null; + private info: SiteEngagementDetails[]|null = null; + engagementDetailsProvider: SiteEngagementDetailsProviderInterface = + SiteEngagementDetailsProvider.getRemote(); + private updateInterval: number|null = null; + private sortKey: string = 'totalScore'; + private sortReverse: boolean = true; + private whenPopulatedResolver: PromiseResolver<void> = new PromiseResolver(); + + connectedCallback() { + const engagementTableHeader = + this.getRequiredElement<HTMLElement>('#engagement-table-header'); + this.engagementTableBody = + this.getRequiredElement<HTMLElement>('#engagement-table-body')!; + + const headers = engagementTableHeader.children; + for (let i = 0; i < headers.length; i++) { + headers[i]!.addEventListener('click', e => { + const target = e.target as HTMLElement; + const newSortKey = target.getAttribute('sort-key'); + assert(newSortKey); + if (this.sortKey === newSortKey) { + this.sortReverse = !this.sortReverse; + } else { + this.sortKey = newSortKey; + this.sortReverse = false; + } + const oldSortColumn = + this.getRequiredElement<HTMLElement>('.sort-column'); + oldSortColumn.classList.remove('sort-column'); + target.classList.add('sort-column'); + target.toggleAttribute('sort-reverse', this.sortReverse); + this.renderTable(); + }); + } + + this.updateEngagementTable(); + this.enableAutoupdate(); + } + + /** + * Creates a single row in the engagement table. + * @param info The info to create the row from. + */ + private createRow(info: SiteEngagementDetails): HTMLElement { + const originCell = document.createElement('td'); + originCell.classList.add('origin-cell'); + originCell.textContent = info.origin.url; + + const baseScoreInput = document.createElement('input'); + baseScoreInput.classList.add('base-score-input'); + baseScoreInput.addEventListener('focus', () => this.disableAutoupdate()); + baseScoreInput.addEventListener('blur', () => this.enableAutoupdate()); + baseScoreInput.value = String(info.baseScore); + + const baseScoreCell = document.createElement('td'); + baseScoreCell.classList.add('base-score-cell'); + baseScoreCell.appendChild(baseScoreInput); + + const bonusScoreCell = document.createElement('td'); + bonusScoreCell.classList.add('bonus-score-cell'); + bonusScoreCell.textContent = String(info.installedBonus); + + const totalScoreCell = document.createElement('td'); + totalScoreCell.classList.add('total-score-cell'); + totalScoreCell.textContent = String(info.totalScore); + + const engagementBar = document.createElement('div'); + engagementBar.classList.add('engagement-bar'); + engagementBar.style.width = (info.totalScore * 4) + 'px'; + + const engagementBarCell = document.createElement('td'); + engagementBarCell.classList.add('engagement-bar-cell'); + engagementBarCell.appendChild(engagementBar); + + const row = document.createElement('tr'); + row.appendChild(originCell); + row.appendChild(baseScoreCell); + row.appendChild(bonusScoreCell); + row.appendChild(totalScoreCell); + row.appendChild(engagementBarCell); + + baseScoreInput.addEventListener( + 'change', + (e: Event) => + this.handleBaseScoreChange(info.origin, engagementBar, e)); + + return row; + } + + disableAutoupdate() { + if (this.updateInterval) { + clearInterval(this.updateInterval); + } + this.updateInterval = null; + } + + private enableAutoupdate() { + if (this.updateInterval) { + clearInterval(this.updateInterval); + } + this.updateInterval = setInterval(() => this.updateEngagementTable(), 5000); + } + + /** + * Sets the base engagement score when a score input is changed. + * Resets the length of engagement-bar-cell to match the new score. + * Also resets the update interval. + * @param origin The origin of the engagement score to set. + */ + private handleBaseScoreChange(origin: Url, barCell: HTMLElement, e: Event) { + const baseScoreInput = e.target as HTMLInputElement; + this.engagementDetailsProvider.setSiteEngagementBaseScoreForUrl( + origin, parseFloat(baseScoreInput.value)); + barCell.style.width = (parseFloat(baseScoreInput.value) * 4) + 'px'; + baseScoreInput.blur(); + this.enableAutoupdate(); + } + + /** + * Adds a new origin with the given base score. + * @param originInput The text input containing the origin to add. + * @param scoreInput The text input containing the score to add. + */ + private handleAddOrigin( + originInput: HTMLInputElement, scoreInput: HTMLInputElement) { + try { + // Validate the URL. If we don't validate here, IPC will kill this + // renderer on invalid URLs. Other checks like scheme are done on the + // browser side. + new URL(originInput.value); + } catch { + return; + } + const origin = new Url(); + origin.url = originInput.value; + const score = parseFloat(scoreInput.value); + + this.engagementDetailsProvider.setSiteEngagementBaseScoreForUrl( + origin, score); + scoreInput.blur(); + this.updateEngagementTable(); + this.enableAutoupdate(); + } + + /** + * Remove all rows from the engagement table. + */ + private clearTable() { + assert(this.engagementTableBody); + this.engagementTableBody.innerHTML = window.trustedTypes!.emptyHTML; + } + + /** + * Sort the engagement info based on |sortKey| and |sortReverse|. + */ + private sortInfo() { + assert(this.info); + this.info.sort((a, b) => { + return (this.sortReverse ? -1 : 1) * compareTableItem(this.sortKey, a, b); + }); + } + + /** + * Regenerates the engagement table from |info|. + */ + private renderTable() { + this.clearTable(); + this.sortInfo(); + + assert(this.info); + this.info.forEach((info) => { + // Round all scores to 2 decimal places. + info.baseScore = roundScore(info.baseScore); + info.installedBonus = roundScore(info.installedBonus); + info.totalScore = roundScore(info.totalScore); + + assert(this.engagementTableBody); + this.engagementTableBody.appendChild(this.createRow(info)); + }); + + // Add another row for adding a new origin. + const originInput = document.createElement('input'); + originInput.classList.add('origin-input'); + originInput.addEventListener('focus', () => this.disableAutoupdate()); + originInput.addEventListener('blur', () => this.enableAutoupdate()); + originInput.value = 'http://example.com'; + + const originCell = document.createElement('td'); + originCell.appendChild(originInput); + + const baseScoreInput = document.createElement('input'); + baseScoreInput.classList.add('base-score-input'); + baseScoreInput.addEventListener('focus', () => this.disableAutoupdate()); + baseScoreInput.addEventListener('blur', () => this.enableAutoupdate()); + baseScoreInput.value = '0'; + + const baseScoreCell = document.createElement('td'); + baseScoreCell.classList.add('base-score-cell'); + baseScoreCell.appendChild(baseScoreInput); + + const addButton = document.createElement('button'); + addButton.textContent = 'Add'; + addButton.classList.add('add-origin-button'); + + const buttonCell = document.createElement('td'); + buttonCell.colSpan = 2; + buttonCell.classList.add('base-score-cell'); + buttonCell.appendChild(addButton); + + const row = document.createElement('tr'); + row.appendChild(originCell); + row.appendChild(baseScoreCell); + row.appendChild(buttonCell); + addButton.addEventListener( + 'click', () => this.handleAddOrigin(originInput, baseScoreInput)); + + assert(this.engagementTableBody); + this.engagementTableBody.appendChild(row); + } + + /** + * Retrieve site engagement info and render the engagement table. + */ + private async updateEngagementTable() { + // Populate engagement table. + this.info = + (await this.engagementDetailsProvider.getSiteEngagementDetails()).info; + this.renderTable(); + this.whenPopulatedResolver.resolve(); + } + + whenPopulatedForTest() { + return this.whenPopulatedResolver.promise; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'site-engagement-app': SiteEngagementAppElement; + } +} + +customElements.define(SiteEngagementAppElement.is, SiteEngagementAppElement);
diff --git a/chrome/browser/resources/engagement/site_engagement.html b/chrome/browser/resources/engagement/site_engagement.html index a81bf12..d685935 100644 --- a/chrome/browser/resources/engagement/site_engagement.html +++ b/chrome/browser/resources/engagement/site_engagement.html
@@ -4,119 +4,15 @@ <meta charset="utf-8"> <meta name="color-scheme" content="light dark"> <link rel="stylesheet" href="chrome://resources/css/text_defaults.css"> - <script src="site_engagement.js" type="module"></script> <style> - :root { - --color-row-hover: rgb(255, 255, 187); - --color-engagement-bar: black; - } - - @media (prefers-color-scheme: dark) { - :root { - --color-row-hover: rgb(3, 220, 176); - --color-engagement-bar: white; - } - } body { font-family: 'Roboto', 'Noto', sans-serif; font-size: 14px; } - - table { - border-collapse: collapse; - } - - table td, - table th { - border: 1px solid #777; - padding-left: 4px; - padding-right: 4px; - } - - table th { - background: rgb(224, 236, 255); - color: black; - cursor: pointer; - padding-bottom: 4px; - padding-right: 16px; - padding-top: 4px; - white-space: nowrap; - } - - .engagement-bar { - background-color: var(--color-engagement-bar); - height: 2px; - } - - .engagement-bar-cell { - border: none; - } - - .origin-cell { - background-color: rgba(230, 230, 230, 0.5); - min-width: 500px; - } - - .base-score-cell, - .bonus-score-cell, - .total-score-cell { - background-color: rgba(230, 230, 230, 0.5); - text-align: right; - } - - .base-score-input { - border: 1px solid #ccc; - border-radius: 2px; - text-align: right; - width: 70px; - } - - .base-score-input:focus { - border: 1px solid rgb(143, 185, 252); - box-shadow: 0 0 2px rgb(113, 158, 206); - outline: none; - } - - .add-origin-button { - width: 10em; - } - - table tr:hover { - background: var(--color-row-hover); - } - - th.sort-column::after { - content: 'â–²'; - position: absolute; - } - - th[sort-reverse].sort-column::after { - content: 'â–¼'; - position: absolute; - } </style> </head> <body> - <h1>Site Engagement</h1> - <table> - <thead> - <tr id="engagement-table-header"> - <th sort-key="origin"> - Origin - </th> - <th sort-key="baseScore" sort-reverse> - Base - </th> - <th sort-key="bonusScore" sort-reverse> - Bonus - </th> - <th sort-key="totalScore" class="sort-column" sort-reverse> - Total - </th> - </tr> - </thead> - <tbody id="engagement-table-body"> - </tbody> - </table> + <script src="app.js" type="module"></script> + <site-engagement-app></site-engagement-app> </body> </html>
diff --git a/chrome/browser/resources/engagement/site_engagement.ts b/chrome/browser/resources/engagement/site_engagement.ts deleted file mode 100644 index ff92fa43..0000000 --- a/chrome/browser/resources/engagement/site_engagement.ts +++ /dev/null
@@ -1,281 +0,0 @@ -// Copyright 2015 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js'; -import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js'; -import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js'; - -import {SiteEngagementDetails, SiteEngagementDetailsProvider} from './site_engagement_details.mojom-webui.js'; - -const pageIsPopulatedResolver = new PromiseResolver<void>(); - -const whenPageIsPopulatedForTest = function() { - return pageIsPopulatedResolver.promise; -}; - -function initialize() { - const engagementDetailsProvider = SiteEngagementDetailsProvider.getRemote(); - - const engagementTableBody = - document.body.querySelector<HTMLElement>('#engagement-table-body')!; - let updateInterval: number|null = null; - let info: SiteEngagementDetails[]|null = null; - let sortKey: string = 'totalScore'; - let sortReverse: boolean = true; - - // Set table header sort handlers. - const engagementTableHeader = - document.body.querySelector<HTMLElement>('#engagement-table-header'); - assert(engagementTableHeader); - const headers = engagementTableHeader.children; - for (let i = 0; i < headers.length; i++) { - headers[i]!.addEventListener('click', e => { - const target = e.target as HTMLElement; - const newSortKey = target.getAttribute('sort-key'); - assert(newSortKey); - if (sortKey === newSortKey) { - sortReverse = !sortReverse; - } else { - sortKey = newSortKey; - sortReverse = false; - } - const oldSortColumn = document.querySelector<HTMLElement>('.sort-column'); - assert(oldSortColumn); - oldSortColumn.classList.remove('sort-column'); - target.classList.add('sort-column'); - target.toggleAttribute('sort-reverse', sortReverse); - renderTable(); - }); - } - - /** - * Creates a single row in the engagement table. - * @param info The info to create the row from. - */ - function createRow(info: SiteEngagementDetails): HTMLElement { - const originCell = document.createElement('td'); - originCell.classList.add('origin-cell'); - originCell.textContent = info.origin.url; - - const baseScoreInput = document.createElement('input'); - baseScoreInput.classList.add('base-score-input'); - baseScoreInput.addEventListener('focus', disableAutoupdate); - baseScoreInput.addEventListener('blur', enableAutoupdate); - baseScoreInput.value = String(info.baseScore); - - const baseScoreCell = document.createElement('td'); - baseScoreCell.classList.add('base-score-cell'); - baseScoreCell.appendChild(baseScoreInput); - - const bonusScoreCell = document.createElement('td'); - bonusScoreCell.classList.add('bonus-score-cell'); - bonusScoreCell.textContent = String(info.installedBonus); - - const totalScoreCell = document.createElement('td'); - totalScoreCell.classList.add('total-score-cell'); - totalScoreCell.textContent = String(info.totalScore); - - const engagementBar = document.createElement('div'); - engagementBar.classList.add('engagement-bar'); - engagementBar.style.width = (info.totalScore * 4) + 'px'; - - const engagementBarCell = document.createElement('td'); - engagementBarCell.classList.add('engagement-bar-cell'); - engagementBarCell.appendChild(engagementBar); - - const row = document.createElement('tr'); - row.appendChild(originCell); - row.appendChild(baseScoreCell); - row.appendChild(bonusScoreCell); - row.appendChild(totalScoreCell); - row.appendChild(engagementBarCell); - - baseScoreInput.addEventListener( - 'change', - handleBaseScoreChange.bind(undefined, info.origin, engagementBar)); - - return row; - } - - function disableAutoupdate() { - if (updateInterval) { - clearInterval(updateInterval); - } - updateInterval = null; - } - - function enableAutoupdate() { - if (updateInterval) { - clearInterval(updateInterval); - } - updateInterval = setInterval(updateEngagementTable, 5000); - } - - /** - * Sets the base engagement score when a score input is changed. - * Resets the length of engagement-bar-cell to match the new score. - * Also resets the update interval. - * @param origin The origin of the engagement score to set. - */ - function handleBaseScoreChange(origin: Url, barCell: HTMLElement, e: Event) { - const baseScoreInput = e.target as HTMLInputElement; - engagementDetailsProvider.setSiteEngagementBaseScoreForUrl( - origin, parseFloat(baseScoreInput.value)); - barCell.style.width = (parseFloat(baseScoreInput.value) * 4) + 'px'; - baseScoreInput.blur(); - enableAutoupdate(); - } - - /** - * Adds a new origin with the given base score. - * @param originInput The text input containing the origin to add. - * @param scoreInput The text input containing the score to add. - */ - function handleAddOrigin( - originInput: HTMLInputElement, scoreInput: HTMLInputElement) { - try { - // Validate the URL. If we don't validate here, IPC will kill this - // renderer on invalid URLs. Other checks like scheme are done on the - // browser side. - new URL(originInput.value); - } catch { - return; - } - const origin = new Url(); - origin.url = originInput.value; - const score = parseFloat(scoreInput.value); - - engagementDetailsProvider.setSiteEngagementBaseScoreForUrl(origin, score); - scoreInput.blur(); - updateEngagementTable(); - enableAutoupdate(); - } - - /** - * Remove all rows from the engagement table. - */ - function clearTable() { - engagementTableBody.innerHTML = window.trustedTypes!.emptyHTML; - } - - /** - * Sort the engagement info based on |sortKey| and |sortReverse|. - */ - function sortInfo() { - assert(info); - info.sort((a, b) => { - return (sortReverse ? -1 : 1) * compareTableItem(sortKey, a, b); - }); - } - - /** - * Compares two SiteEngagementDetails objects based on |sortKey|. - * @param sortKey The name of the property to sort by. - * @return A negative number if |a| should be ordered before |b|, a - * positive number otherwise. - */ - function compareTableItem( - sortKey: string, a: {[k: string]: any}, b: {[k: string]: any}): number { - const val1 = a[sortKey]; - const val2 = b[sortKey]; - - // Compare the hosts of the origin ignoring schemes. - if (sortKey === 'origin') { - return new URL(val1.url).host > new URL(val2.url).host ? 1 : -1; - } - - if (sortKey === 'baseScore' || sortKey === 'bonusScore' || - sortKey === 'totalScore') { - return val1 - val2; - } - - assertNotReached('Unsupported sort key: ' + sortKey); - } - - /** - * Rounds the supplied value to two decimal places of accuracy. - */ - function roundScore(score: number): number { - return Number(Math.round(score * 100) / 100); - } - - /** - * Regenerates the engagement table from |info|. - */ - function renderTable() { - clearTable(); - sortInfo(); - - assert(info); - info.forEach((info) => { - // Round all scores to 2 decimal places. - info.baseScore = roundScore(info.baseScore); - info.installedBonus = roundScore(info.installedBonus); - info.totalScore = roundScore(info.totalScore); - - engagementTableBody.appendChild(createRow(info)); - }); - - // Add another row for adding a new origin. - const originInput = document.createElement('input'); - originInput.classList.add('origin-input'); - originInput.addEventListener('focus', disableAutoupdate); - originInput.addEventListener('blur', enableAutoupdate); - originInput.value = 'http://example.com'; - - const originCell = document.createElement('td'); - originCell.appendChild(originInput); - - const baseScoreInput = document.createElement('input'); - baseScoreInput.classList.add('base-score-input'); - baseScoreInput.addEventListener('focus', disableAutoupdate); - baseScoreInput.addEventListener('blur', enableAutoupdate); - baseScoreInput.value = '0'; - - const baseScoreCell = document.createElement('td'); - baseScoreCell.classList.add('base-score-cell'); - baseScoreCell.appendChild(baseScoreInput); - - const addButton = document.createElement('button'); - addButton.textContent = 'Add'; - addButton.classList.add('add-origin-button'); - - const buttonCell = document.createElement('td'); - buttonCell.colSpan = 2; - buttonCell.classList.add('base-score-cell'); - buttonCell.appendChild(addButton); - - const row = document.createElement('tr'); - row.appendChild(originCell); - row.appendChild(baseScoreCell); - row.appendChild(buttonCell); - addButton.addEventListener( - 'click', () => handleAddOrigin(originInput, baseScoreInput)); - - engagementTableBody.appendChild(row); - } - - /** - * Retrieve site engagement info and render the engagement table. - */ - async function updateEngagementTable() { - // Populate engagement table. - ({info} = await engagementDetailsProvider.getSiteEngagementDetails()); - renderTable(); - pageIsPopulatedResolver.resolve(); - } - - updateEngagementTable(); - enableAutoupdate(); - - // We explicitly set these on the global Window object so test code can use - // them. - Object.assign(window, { - whenPageIsPopulatedForTest, - disableAutoupdateForTests: disableAutoupdate, - engagementDetailsProvider, - }); -} - -document.addEventListener('DOMContentLoaded', initialize);
diff --git a/chrome/browser/resources/hats/BUILD.gn b/chrome/browser/resources/hats/BUILD.gn new file mode 100644 index 0000000..5bd97fc --- /dev/null +++ b/chrome/browser/resources/hats/BUILD.gn
@@ -0,0 +1,17 @@ +# Copyright 2023 The Chromium Authors +# Use of this source code is governed by a BSD - style license that can be +# found in the LICENSE file. + +import("//ui/webui/resources/tools/build_webui.gni") + +assert(!is_android) + +build_webui("build") { + grd_prefix = "hats" + + static_files = [ "hats.html" ] + + non_web_component_files = [ "hats.ts" ] + + ts_deps = [ "//ui/webui/resources/js:build_ts" ] +}
diff --git a/chrome/browser/resources/hats/DIR_METADATA b/chrome/browser/resources/hats/DIR_METADATA new file mode 100644 index 0000000..4b06f183 --- /dev/null +++ b/chrome/browser/resources/hats/DIR_METADATA
@@ -0,0 +1,3 @@ +monorail { + component: "UI>Browser>HaTS" +}
diff --git a/chrome/browser/resources/hats/OWNERS b/chrome/browser/resources/hats/OWNERS new file mode 100644 index 0000000..bbdb9fd --- /dev/null +++ b/chrome/browser/resources/hats/OWNERS
@@ -0,0 +1 @@ +file://chrome/browser/ui/hats/OWNERS
diff --git a/chrome/browser/resources/hats/hats.html b/chrome/browser/resources/hats/hats.html new file mode 100644 index 0000000..40c9128 --- /dev/null +++ b/chrome/browser/resources/hats/hats.html
@@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html> + +<head> + <meta charset="utf-8"> + <style> + body { + margin: 0; + } + </style> +</head> + +<body> + <script type="module" src="hats.js"></script> + <h1>HaTS</h1> + <div id="example-div">This is a sample survey.</div> +</body> + +</html>
diff --git a/chrome/browser/resources/hats/hats.ts b/chrome/browser/resources/hats/hats.ts new file mode 100644 index 0000000..ef76a59 --- /dev/null +++ b/chrome/browser/resources/hats/hats.ts
@@ -0,0 +1,7 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function initialize() {} + +document.addEventListener('DOMContentLoaded', initialize);
diff --git a/chrome/browser/resources/nearby_internals/BUILD.gn b/chrome/browser/resources/nearby_internals/BUILD.gn index 1198a30..4a9225d 100644 --- a/chrome/browser/resources/nearby_internals/BUILD.gn +++ b/chrome/browser/resources/nearby_internals/BUILD.gn
@@ -134,6 +134,7 @@ js_library("cross_device_internals") { deps = [ + ":logging_tab", ":nearby_presence_browser_proxy", ":np_list_object", ":types",
diff --git a/chrome/browser/resources/nearby_internals/cross_device_internals.html b/chrome/browser/resources/nearby_internals/cross_device_internals.html index 38a4da0e..9ee46bb 100644 --- a/chrome/browser/resources/nearby_internals/cross_device_internals.html +++ b/chrome/browser/resources/nearby_internals/cross_device_internals.html
@@ -5,64 +5,23 @@ Until it is complete, cross-device-internals will be used as a part of the ui-triggers-tab. --> -<style include="shared-style"> - cr-button { - height: 35px; - padding: 5px; - width: 150px; - } +<style include="shared-style md-select cros-color-overrides"></style> - #buttons { - margin-bottom: 80px; - display: flex; - } +<body> + <section id='controls'> + <header> + <div class='title'>Cross Device Internals</div> + </header> + <div id='controls-panel'> + <div class='control'> - #header { - padding: 20px; - } + <div id="actions"> + <div class="select-div"> + </div> + </div> + </div> + </div> + </section> - #devicesContainer { - display: flex; - flex-direction: column; - height: 100%; - padding: 6px; - white-space: pre-wrap; - } - - np-object:last-child { - border-bottom: var(--standard-border); - } - - np-object { - border-inline-end: var(--standard-border); - border-inline-start: var(--standard-border); - border-top: var(--standard-border); - } - -</style> - -<div id="buttons"> - <cr-button class="internals-button" on-click="onStartScanClicked"> - Presence: Start scan - </cr-button> - - <cr-button class="internals-button" on-click="onStopScanClicked"> - Presence: Stop scan - </cr-button> - - <cr-button class="internals-button" on-click="onSyncCredentialsClicked"> - Presence: Sync Credentials - </cr-button> - - <cr-button class="internals-button" on-click="onFirstTimeFlowClicked"> - Presence: First time flow - </cr-button> -</div> - -<iron-list items="[[npDiscoveredDevicesList_]]" as="generic-object" - id="devicesContainer"> - <template> - <np-object device="[[generic-object]]" > - </np-object> - </template> -</iron-list> + <logging-tab id="logs-panel" feature="nearby-share"></logging-tab> +</body>
diff --git a/chrome/browser/resources/nearby_internals/cross_device_internals.js b/chrome/browser/resources/nearby_internals/cross_device_internals.js index b063d23..5f812101 100644 --- a/chrome/browser/resources/nearby_internals/cross_device_internals.js +++ b/chrome/browser/resources/nearby_internals/cross_device_internals.js
@@ -6,6 +6,7 @@ import 'chrome://resources/cr_elements/cr_shared_vars.css.js'; import './shared_style.css.js'; import './np_list_object.js'; +import './logging_tab.js'; import {WebUIListenerBehavior} from 'chrome://resources/ash/common/web_ui_listener_behavior.js'; import {Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/nearby_internals/index.html b/chrome/browser/resources/nearby_internals/index.html index eb4fd150..c1c5e69b 100644 --- a/chrome/browser/resources/nearby_internals/index.html +++ b/chrome/browser/resources/nearby_internals/index.html
@@ -5,13 +5,21 @@ <base href="chrome://nearby-internals"> <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css"> <style> - body { + html, body { margin: 0; + height: 100%; + } + + cross-device-internals { + display: flex; + flex-direction: row; + height: 100%; + width: 100%; } </style> </head> <body> - <nearby-internals></nearby-internals> - <script type="module" src="nearby_internals.js"></script> + <cross-device-internals></cross-device-internals> + <script type="module" src="cross_device_internals.js"></script> </body> </html>
diff --git a/chrome/browser/resources/nearby_internals/shared_style.css b/chrome/browser/resources/nearby_internals/shared_style.css index 6958db6..9e5559ca 100644 --- a/chrome/browser/resources/nearby_internals/shared_style.css +++ b/chrome/browser/resources/nearby_internals/shared_style.css
@@ -20,3 +20,52 @@ font-family: Roboto; margin: 5px; } + +.flex { + flex: 1; +} + +header { + min-height: 50px; + font-size: 24px; + background-color: #069BDE; + display: flex; + padding: 0 20px; + color: #f4f4f4; + text-align: center; + align-items: center; + border-bottom: 1px solid rgba(0,0,0,0.12); +} + +.hidden { + display: none; +} + +/** CSS for controls panel */ +#controls, #logs { + width: 50%; + display: flex; + flex-direction: column; + height: 100%; +} + +.logo { + font-size: 30px; + margin-right: 12px; +} + +.control-title { + font-size: 18px; + font-weight: bold; + margin-bottom: 5px; +} + +/** CSS for logs panel */ +#logs-panel { + width: 50%; + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + border-left: 1px solid rgba(0,0,0,0.12); +}
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/cart/cart_tile.html b/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/cart/cart_tile.html index 1bb0f5a5..6f24854 100644 --- a/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/cart/cart_tile.html +++ b/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/cart/cart_tile.html
@@ -16,7 +16,7 @@ } a:hover { - background-color: var(--color-sys-state-hover-on-subtle); + background-color: var(--color-sys-state-hover-dim-blend-protection); border-radius: var(--ntp-module-item-border-radius); }
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/module.html b/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/module.html index 5d3a74a92..b2cfeee 100644 --- a/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/module.html +++ b/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/module.html
@@ -104,12 +104,17 @@ } .related-search:hover { - background: var(--color-sys-state-hover-on-subtle); + background: var(--color-sys-state-hover-dim-blend-protection); } - .related-search:active, - :host-context(.focus-outline-visible) .related-search:focus { - background-color: var(--color-new-tab-page-active-background); + .related-search:active { + background-color: var(--color-new-tab-page-active-background); + } + + :host-context(.focus-outline-visible) .related-search:focus, + .related-search:focus-visible { + box-shadow: var(--ntp-focus-shadow); + outline: none; } #first-related-search { @@ -117,20 +122,6 @@ var(--ntp-module-item-border-radius) 0 0; } - #first-related-search:hover { - background: var(--color-sys-state-hover-dim-blend-protection); - } - - #first-related-search:active { - background-color: var(--color-new-tab-page-active-background); - } - - :host-context(.focus-outline-visible) #first-related-search:focus, - #first-related-search:focus-visible { - box-shadow: var(--ntp-focus-shadow); - outline: none; - } - #related-searches-divider { border-bottom: 1px solid var(--color-new-tab-page-module-background); @@ -151,20 +142,6 @@ var(--ntp-module-item-border-radius); } - #last-related-search:hover { - background: var(--color-sys-state-hover-dim-blend-protection); - } - - #last-related-search:active { - background-color: var(--color-new-tab-page-active-background); - } - - :host-context(.focus-outline-visible) #last-related-search:focus, - #last-related-search:focus-visible { - box-shadow: var(--ntp-focus-shadow); - outline: none; - } - #related-searches { background: var(--color-new-tab-page-module-item-background); border-radius: var(--ntp-module-item-border-radius);
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/visit_tile.html b/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/visit_tile.html index 5ea5698a..23ade6e 100644 --- a/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/visit_tile.html +++ b/chrome/browser/resources/new_tab_page/modules/v2/history_clusters/visit_tile.html
@@ -16,7 +16,7 @@ } a:hover { - background: var(--color-sys-state-hover-on-subtle); + background: var(--color-sys-state-hover-dim-blend-protection); border-radius: var(--ntp-module-item-border-radius); }
diff --git a/chrome/browser/resources/side_panel/customize_chrome/app.html b/chrome/browser/resources/side_panel/customize_chrome/app.html index 0651923..331b3b88 100644 --- a/chrome/browser/resources/side_panel/customize_chrome/app.html +++ b/chrome/browser/resources/side_panel/customize_chrome/app.html
@@ -91,6 +91,14 @@ <customize-chrome-cards></customize-chrome-cards> </div> </template> + <template is="dom-if" if="[[extensionsCardEnabled_]]" restamp> + <hr class="sp-cards-separator"> + <div id="extensions" class="section sp-card"> + <sp-heading hide-back-button> + <h2 slot="heading">Extensions</h2> + </sp-heading> + </div> + </template> </div> <customize-chrome-categories on-back-click="onBackClick_" on-collection-select="onCollectionSelect_" page-name="categories"
diff --git a/chrome/browser/resources/side_panel/customize_chrome/app.ts b/chrome/browser/resources/side_panel/customize_chrome/app.ts index 46d16db..64e8f44 100644 --- a/chrome/browser/resources/side_panel/customize_chrome/app.ts +++ b/chrome/browser/resources/side_panel/customize_chrome/app.ts
@@ -78,6 +78,10 @@ type: Object, value: null, }, + extensionsCardEnabled_: { + type: Boolean, + value: () => loadTimeData.getBoolean('extensionsCardEnabled'), + }, }; }
diff --git a/chrome/browser/resources/side_panel/read_anything/app.ts b/chrome/browser/resources/side_panel/read_anything/app.ts index 6a62f3e3..e6f24a5 100644 --- a/chrome/browser/resources/side_panel/read_anything/app.ts +++ b/chrome/browser/resources/side_panel/read_anything/app.ts
@@ -407,7 +407,7 @@ }); } - updateLetterSpacing(newLetterSpacing: string) { + updateLetterSpacing(newLetterSpacing: number) { this.updateStyles({ '--letter-spacing': newLetterSpacing + 'em', }); @@ -429,6 +429,13 @@ } } + updateFontSize(increase: boolean) { + chrome.readingMode.onFontSizeChanged(increase); + this.updateStyles({ + '--font-size': chrome.readingMode.fontSize + 'em', + }); + } + // TODO(crbug.com/1465029): This method should be renamed to updateTheme() // and replace the one below once we've removed the Views toolbar. updateThemeFromWebUi(colorSuffix: string) {
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts b/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts index 56a3252..11308fa 100644 --- a/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts +++ b/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts
@@ -35,6 +35,9 @@ let standardLineSpacing: number; let looseLineSpacing: number; let veryLooseLineSpacing: number; + let standardLetterSpacing: number; + let wideLetterSpacing: number; + let veryWideLetterSpacing: number; // Whether the WebUI toolbar feature flag is enabled. let isWebUIToolbarVisible: boolean; @@ -92,10 +95,32 @@ function onLooseLineSpacing(): void; function onVeryLooseLineSpacing(): void; + // Called when a user makes a font size change via the webui toolbar. + function onFontSizeChanged(increase: boolean): void; + + // Called when the letter spacing is changed via the webui toolbar. + function onStandardLetterSpacing(): void; + function onWideLetterSpacing(): void; + function onVeryWideLetterSpacing(): void; + + // Called when the color theme is changed via the webui toolbar. + function onDefaultTheme(): void; + function onLightTheme(): void; + function onDarkTheme(): void; + function onYellowTheme(): void; + function onBlueTheme(): void; + + // Called when the font is changed via the webui toolbar. + function onFontChange(font: string): void; + // Returns the actual spacing value to use based on the given lineSpacing // category. function getLineSpacingValue(lineSpacing: number): number; + // Returns the actual spacing value to use based on the given letterSpacing + // category. + function getLetterSpacingValue(letterSpacing: number): number; + // Called when a user makes a selection change. AnchorNodeID and // focusAXNodeID are AXNodeIDs which identify the anchor and focus AXNodes // in the main pane. The selection can either be forward or backwards.
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html index 3782590..22dbb57d 100644 --- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html +++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html
@@ -62,9 +62,15 @@ </iron-icon> $i18n{fontSizeTitle} <span class="end-image"> - <cr-icon-button class="floating-button button-image" iron-icon="read-anything:font-size-increase"> + <cr-icon-button + class="floating-button button-image" + on-click="onFontSizeIncreaseClick_" + iron-icon="read-anything:font-size-increase"> </cr-icon-button> - <cr-icon-button class="floating-button button-image" iron-icon="read-anything:font-size-decrease"> + <cr-icon-button + class="floating-button button-image" + on-click="onFontSizeDecreaseClick_" + iron-icon="read-anything:font-size-decrease"> </cr-icon-button> </span> </span> @@ -128,17 +134,17 @@ <iron-icon class="button-image" icon="cr:arrow-back"></iron-icon> $i18n{back} </button> - <button class="dropdown-item" on-click="onLineSpacingStandardClick_"> + <button class="dropdown-item" data$="[[menuStateEnum_.LINE_STANDARD]]"> <iron-icon class="button-image" icon="read-anything:line-spacing-standard"></iron-icon> $i18n{lineSpacingStandardTitle} </button> - <button class="dropdown-item" on-click="onLineSpacingLooseClick_"> + <button class="dropdown-item" data$="[[menuStateEnum_.LOOSE]]"> <iron-icon class="button-image" icon="read-anything:line-spacing-loose"></iron-icon> $i18n{lineSpacingLooseTitle} </button> - <button class="dropdown-item" on-click="onLineSpacingVeryLooseClick_"> + <button class="dropdown-item" data$="[[menuStateEnum_.VERY_LOOSE]]"> <iron-icon class="button-image" icon="read-anything:line-spacing-very-loose"></iron-icon> $i18n{lineSpacingVeryLooseTitle}
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts index cf7a5a3..56bdfdf 100644 --- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts +++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
@@ -25,6 +25,12 @@ }; } +enum MenuStateValue { + LINE_STANDARD = 0, + LOOSE = 1, + VERY_LOOSE = 2, +} + const ReadAnythingToolbarBase = WebUiListenerMixin(PolymerElement); export class ReadAnythingToolbar extends ReadAnythingToolbarBase { @@ -38,7 +44,12 @@ } static get properties() { - return {}; + return { + menuStateEnum_: { + type: Object, + value: MenuStateValue, + }, + }; } private showAtPositionConfig_: ShowAtPositionConfig = { @@ -47,6 +58,10 @@ anchorAlignmentY: AnchorAlignment.AFTER_END, }; + // This is needed to keep a reference to any dynamically added callbacks so + // that they can be removed with #removeEventListener. + private elementCallbackMap = new Map<any, () => void>(); + override connectedCallback() { super.connectedCallback(); @@ -69,25 +84,94 @@ }); } }); + + // Configure on-click listeners for line spacing. + const onLineSpacingClick = (element: number) => { + let data: number|undefined; + + switch (element) { + case MenuStateValue.LINE_STANDARD: + chrome.readingMode.onStandardLineSpacing(); + data = chrome.readingMode.standardLineSpacing; + break; + case MenuStateValue.LOOSE: + chrome.readingMode.onLooseLineSpacing(); + data = chrome.readingMode.looseLineSpacing; + break; + case MenuStateValue.VERY_LOOSE: + chrome.readingMode.onVeryLooseLineSpacing(); + data = chrome.readingMode.veryLooseLineSpacing; + break; + default: + // Do nothing; + } + + if (this.contentPage && data) { + this.contentPage.updateLineSpacing( + chrome.readingMode.getLineSpacingValue(data)); + } + this.closeMenus_(); + }; + this.addOnClickListeners(this.$.lineSpacingSubmenu, onLineSpacingClick); + } + + override disconnectedCallback() { + super.disconnectedCallback(); + this.removeOnClickListeners(this.$.lineSpacingSubmenu); + } + + private removeOnClickListeners(menu: CrActionMenuElement) { + const nodes = Array.from(menu.children); + nodes.forEach((element) => { + if ((element instanceof HTMLButtonElement) && + !element.classList.contains('back') && element.hasAttribute('data')) { + const callback = this.elementCallbackMap.get(element); + if (callback) { + element.removeEventListener('click', callback); + } + this.elementCallbackMap.delete(element); + } + }); + } + + private addOnClickListeners( + menu: CrActionMenuElement, + onMenuElementClick: (element: number) => void) { + const nodes = Array.from(menu.children); + nodes.forEach((element) => { + if ((element instanceof HTMLButtonElement) && + !element.classList.contains('back') && element.hasAttribute('data')) { + const callback = () => { + onMenuElementClick(parseInt(element.getAttribute('data')!)); + }; + this.elementCallbackMap.set(element, callback); + element.addEventListener('click', callback); + } + }); } private onDefaultTheme_() { + chrome.readingMode.onDefaultTheme(); this.updateTheme_(''); } private onLightTheme_() { + chrome.readingMode.onLightTheme(); this.updateTheme_('-light'); } private onDarkTheme_() { + chrome.readingMode.onDarkTheme(); this.updateTheme_('-dark'); } private onBlueTheme_() { + chrome.readingMode.onBlueTheme(); this.updateTheme_('-blue'); } private onYellowTheme_() { + chrome.readingMode.onYellowTheme(); this.updateTheme_('-yellow'); } @@ -140,56 +224,51 @@ menuToOpen.showAt(button, this.showAtPositionConfig_); } - private onLineSpacingStandardClick_() { - chrome.readingMode.onStandardLineSpacing(); - this.onLineSpacingClick_(chrome.readingMode.standardLineSpacing); - } - - private onLineSpacingLooseClick_() { - chrome.readingMode.onLooseLineSpacing(); - this.onLineSpacingClick_(chrome.readingMode.looseLineSpacing); - } - - private onLineSpacingVeryLooseClick_() { - chrome.readingMode.onVeryLooseLineSpacing(); - this.onLineSpacingClick_(chrome.readingMode.veryLooseLineSpacing); - } - - private onLineSpacingClick_(lineSpacing: number) { - if (this.contentPage) { - this.contentPage.updateLineSpacing( - chrome.readingMode.getLineSpacingValue(lineSpacing)); - } - - this.closeMenus_(); - } - private onLetterSpacingStandardClick_() { - this.onLetterSpacingClick_('0'); + chrome.readingMode.onStandardLetterSpacing(); + this.onLetterSpacingClick_(chrome.readingMode.standardLetterSpacing); } private onLetterSpacingWideClick_() { - this.onLetterSpacingClick_('.05'); + chrome.readingMode.onWideLetterSpacing(); + this.onLetterSpacingClick_(chrome.readingMode.wideLetterSpacing); } private onLetterSpacingVeryWideClick_() { - this.onLetterSpacingClick_('.1'); + chrome.readingMode.onVeryWideLetterSpacing(); + this.onLetterSpacingClick_(chrome.readingMode.veryWideLetterSpacing); } - private onLetterSpacingClick_(letterSpacing: string) { + private onLetterSpacingClick_(letterSpacing: number) { if (this.contentPage) { - this.contentPage.updateLetterSpacing(letterSpacing); + this.contentPage.updateLetterSpacing( + chrome.readingMode.getLetterSpacingValue(letterSpacing)); } this.closeMenus_(); } private onFontClick_(fontName: string) { + chrome.readingMode.onFontChange(fontName); if (this.contentPage) { this.contentPage.updateFont(fontName); } this.closeMenus_(); } + + private onFontSizeIncreaseClick_() { + this.updateFontSize_(true); + } + + private onFontSizeDecreaseClick_() { + this.updateFontSize_(false); + } + private updateFontSize_(increase: boolean) { + if (this.contentPage) { + this.contentPage.updateFontSize(increase); + } + // Don't close the menu + } } customElements.define('read-anything-toolbar', ReadAnythingToolbar);
diff --git a/chrome/browser/resources/web_app_internals/web_app_internals.html b/chrome/browser/resources/web_app_internals/web_app_internals.html index 0093c8e9..8074866 100644 --- a/chrome/browser/resources/web_app_internals/web_app_internals.html +++ b/chrome/browser/resources/web_app_internals/web_app_internals.html
@@ -16,6 +16,17 @@ <hr> + <div id="iwa-install-div" style="display: none;"> + <p>Isolated Web Apps</p> + <p>Install IWA via Dev Mode Proxy: + <input type="url" id="iwa-install-url" size="30" required + placeholder="http://localhost:8000/"> + <button id="iwa-install-button" type="submit">Install</button> + </p> + <div id="iwa-install-message-div"></div> + <hr> + </div> + <pre id="json"></pre> </body>
diff --git a/chrome/browser/resources/web_app_internals/web_app_internals.ts b/chrome/browser/resources/web_app_internals/web_app_internals.ts index 20aa077b..cae8b7d 100644 --- a/chrome/browser/resources/web_app_internals/web_app_internals.ts +++ b/chrome/browser/resources/web_app_internals/web_app_internals.ts
@@ -2,7 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import './strings.m.js'; + +import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; import {getRequiredElement} from 'chrome://resources/js/util_ts.js'; +import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js'; import {WebAppInternalsHandler} from './web_app_internals.mojom-webui.js'; @@ -12,6 +16,10 @@ webAppInternalsHandler.getDebugInfoAsJsonString().then( response => response.result); +const iwaInstallButton = + getRequiredElement('iwa-install-button') as HTMLButtonElement; +const iwaInstallUrl = getRequiredElement('iwa-install-url') as HTMLInputElement; + getRequiredElement('copy-button').addEventListener('click', async () => { navigator.clipboard.writeText(await debugInfoAsJsonString); }); @@ -34,6 +42,75 @@ URL.revokeObjectURL(url); }); +function iwaInstallStateUpdate() { + iwaInstallButton.disabled = (iwaInstallUrl.value.length === 0); +} + +async function iwaInstallSubmit() { + iwaInstallButton.disabled = true; + + const iwaInstallMessageDiv = getRequiredElement('iwa-install-message-div'); + + // Validate the provided URL. + let valid = false; + try { + // We don't need the result of this, only to verify it doesn't throw an + // exception. + new URL(iwaInstallUrl.value); + valid = + (iwaInstallUrl.value.startsWith('http:') || + iwaInstallUrl.value.startsWith('https:')); + } catch (_) { + // Fall-through. + } + if (!valid) { + iwaInstallMessageDiv.innerText = + `Installing IWA: ${iwaInstallUrl.value} is not a valid URL`; + iwaInstallStateUpdate(); + return; + } + + iwaInstallMessageDiv.innerText = `Installing IWA: ${iwaInstallUrl.value}...`; + + const location = new Url(); + location.url = iwaInstallUrl.value; + + const installFromDevProxy = + await webAppInternalsHandler.installIsolatedWebAppFromDevProxy(location); + if (installFromDevProxy.result.success) { + iwaInstallMessageDiv.innerText = + `Installing IWA: ${iwaInstallUrl.value} successfully installed.`; + iwaInstallUrl.value = ''; + iwaInstallStateUpdate(); + return; + } + + iwaInstallMessageDiv.innerText = + `Installing IWA: ${iwaInstallUrl.value} failed to install: ${ + installFromDevProxy.result.error}`; + iwaInstallStateUpdate(); +} + +iwaInstallUrl.addEventListener('enter', iwaInstallSubmit); +iwaInstallButton.addEventListener('click', iwaInstallSubmit); + +function updateIwaInstallButtonState(event: KeyboardEvent) { + if (event.key === 'Enter') { + event.preventDefault(); + iwaInstallSubmit(); + return; + } + iwaInstallStateUpdate(); +} +iwaInstallUrl.addEventListener('keyup', updateIwaInstallButtonState); +iwaInstallStateUpdate(); + document.addEventListener('DOMContentLoaded', async () => { getRequiredElement('json').innerText = await debugInfoAsJsonString; + + if (loadTimeData.getBoolean('experimentalIsIwaDevModeEnabled')) { + // Unhide the IWA install div. + getRequiredElement('iwa-install-div').style.display = ''; + return; + } });
diff --git a/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc b/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc index d85f7bc..31957388 100644 --- a/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc +++ b/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc
@@ -66,6 +66,8 @@ CSDModelType GetModelType() override { return CSDModelType::kProtobuf; } + bool IsModelAvailable() override { return true; } + const std::string& GetModelStr() override { client_side_model_ = model_.SerializeAsString(); return client_side_model_;
diff --git a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc index cc9a2a98..24f7ec8 100644 --- a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc +++ b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc
@@ -154,6 +154,7 @@ MOCK_METHOD0(GetModelStr, std::string&()); MOCK_METHOD0(GetModelSharedMemoryRegion, base::ReadOnlySharedMemoryRegion()); MOCK_METHOD0(GetModelType, CSDModelType()); + MOCK_METHOD0(IsModelAvailable, bool()); }; class MockSafeBrowsingUIManager : public SafeBrowsingUIManager {
diff --git a/chrome/browser/safe_browsing/client_side_detection_service_browsertest.cc b/chrome/browser/safe_browsing/client_side_detection_service_browsertest.cc index 090386f8..e7369b9 100644 --- a/chrome/browser/safe_browsing/client_side_detection_service_browsertest.cc +++ b/chrome/browser/safe_browsing/client_side_detection_service_browsertest.cc
@@ -71,19 +71,7 @@ using ::testing::ReturnRef; using ::testing::StrictMock; -class ClientSideDetectionServiceBrowserTest - : public PlatformBrowserTest, - public testing::WithParamInterface<bool> { - public: - ClientSideDetectionServiceBrowserTest() { - if (ShouldEnableCacao()) { - feature_list_.InitAndEnableFeature( - kClientSideDetectionModelOptimizationGuide); - } - } - - bool ShouldEnableCacao() { return GetParam(); } - +class ClientSideDetectionServiceBrowserTest : public PlatformBrowserTest { protected: void SetUpOnMainThread() override { ASSERT_TRUE(embedded_test_server()->Start()); @@ -111,21 +99,15 @@ return waiter; } - - base::test::ScopedFeatureList feature_list_; }; -INSTANTIATE_TEST_SUITE_P(All, - ClientSideDetectionServiceBrowserTest, - testing::Bool()); - // TODO(crbug.com/1434848): Re-enable this test #if BUILDFLAG(IS_CHROMEOS) && !defined(NDEBUG) #define MAYBE_ModelUpdatesPropagated DISABLED_ModelUpdatesPropagated #else #define MAYBE_ModelUpdatesPropagated ModelUpdatesPropagated #endif -IN_PROC_BROWSER_TEST_P(ClientSideDetectionServiceBrowserTest, +IN_PROC_BROWSER_TEST_F(ClientSideDetectionServiceBrowserTest, MAYBE_ModelUpdatesPropagated) { #if BUILDFLAG(IS_MAC) if (base::mac::IsAtLeastOS13()) { @@ -147,44 +129,28 @@ base::RunLoop run_loop; waiter->SetCallback(run_loop.QuitClosure()); + safe_browsing::ClientSideDetectionService* csd_service = + ClientSideDetectionServiceFactory::GetForProfile( + Profile::FromBrowserContext(web_contents()->GetBrowserContext())); + base::FilePath model_file_path; + ASSERT_TRUE( + base::PathService::Get(chrome::DIR_TEST_DATA, &model_file_path)); + model_file_path = model_file_path.AppendASCII("safe_browsing") + .AppendASCII("client_model.pb"); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - safe_browsing::ClientSideDetectionService* csd_service = - ClientSideDetectionServiceFactory::GetForProfile( - Profile::FromBrowserContext(web_contents()->GetBrowserContext())); - base::FilePath model_file_path; - ASSERT_TRUE( - base::PathService::Get(chrome::DIR_TEST_DATA, &model_file_path)); - model_file_path = model_file_path.AppendASCII("safe_browsing") - .AppendASCII("client_model.pb"); - - base::FilePath additional_files_path; - ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, - &additional_files_path)); + base::FilePath additional_files_path; + ASSERT_TRUE( + base::PathService::Get(chrome::DIR_TEST_DATA, &additional_files_path)); #if BUILDFLAG(IS_ANDROID) - additional_files_path = additional_files_path.AppendASCII("safe_browsing") - .AppendASCII("visual_model_android.tflite"); + additional_files_path = additional_files_path.AppendASCII("safe_browsing") + .AppendASCII("visual_model_android.tflite"); #else - additional_files_path = additional_files_path.AppendASCII("safe_browsing") - .AppendASCII("visual_model_desktop.tflite"); + additional_files_path = additional_files_path.AppendASCII("safe_browsing") + .AppendASCII("visual_model_desktop.tflite"); #endif - csd_service->SetModelAndVisualTfLiteForTesting(model_file_path, - additional_files_path); - } else { - ClientSideModel model; - model.set_version(123); - model.set_max_words_per_term(0); - std::string model_str; - model.SerializeToString(&model_str); - - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kProtobuf); - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(model_str); - ClientSidePhishingModel::GetInstance() - ->NotifyCallbacksOfUpdateForTesting(); - } + csd_service->SetModelAndVisualTfLiteForTesting(model_file_path, + additional_files_path); run_loop.Run(); } @@ -216,12 +182,7 @@ ClientPhishingRequest request; ASSERT_TRUE(request.ParseFromString(verdict)); - if (!base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - EXPECT_EQ(123, request.model_version()); - } else { - EXPECT_EQ(27, request.model_version()); // Example model file version - } + EXPECT_EQ(27, request.model_version()); // Example model file version } } @@ -231,7 +192,7 @@ #else #define MAYBE_TfLiteClassification TfLiteClassification #endif -IN_PROC_BROWSER_TEST_P(ClientSideDetectionServiceBrowserTest, +IN_PROC_BROWSER_TEST_F(ClientSideDetectionServiceBrowserTest, MAYBE_TfLiteClassification) { #if BUILDFLAG(IS_MAC) if (base::mac::IsAtLeastOS13()) { @@ -261,71 +222,28 @@ base::RunLoop run_loop; waiter->SetCallback(run_loop.QuitClosure()); - if (!base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - ClientSideModel model; - model.set_version(123); - model.set_max_words_per_term(0); - - model.mutable_tflite_metadata()->set_input_width(48); - model.mutable_tflite_metadata()->set_input_height(48); - - std::vector<std::pair<std::string, double>> thresholds{ - {"502fd246eb6fad3eae0387c54e4ebe74", 2.0}, - {"7c4065b088444b37d273872b771e6940", 2.0}, - {"712036bd72bf185a2a4f88de9141d02d", 2.0}, - {"9e9c15bfa7cb3f8699e2271116a4175c", 2.0}, - {"6c2cb3f559e7a03f37dd873fc007dc65", 2.0}, - {"1cbeb74661a5e7e05c993f2524781611", 2.0}, - {"989790016b6adca9d46b9c8ec6b8fe3a", 2.0}, - {"501067590331ca2d243c669e6084c47e", 2.0}, - {"40aed7e33c100058e54c73af3ed49524", 2.0}, - {"62f53ea23c7ad2590db711235a45fd38", 2.0}, - {"ee6fb9baa44f192bc3c53d8d3c6f7a3d", 2.0}, - {"ea54b0830d871286e2b4023bbb431710", 2.0}, - {"25645a55b844f970337218ea8f1f26b7", 2.0}, - {"c9a8640be09f97f170f1a2708058c48f", 2.0}, - {"953255ea26aa8578d06593ff33e99298", 2.0}}; - for (const auto& label_and_threshold : thresholds) { - TfLiteModelMetadata::Threshold* threshold = - model.mutable_tflite_metadata()->add_thresholds(); - threshold->set_label(label_and_threshold.first); - threshold->set_threshold(label_and_threshold.second); - } - - std::string model_str; - model.SerializeToString(&model_str); - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kProtobuf); - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(model_str); - ClientSidePhishingModel::GetInstance() - ->NotifyCallbacksOfUpdateForTesting(); - - run_loop.Run(); - } else { - base::FilePath tflite_path; - ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &tflite_path)); + base::FilePath tflite_path; + ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &tflite_path)); #if BUILDFLAG(IS_ANDROID) - tflite_path = tflite_path.AppendASCII("safe_browsing") - .AppendASCII("visual_model_android.tflite"); + tflite_path = tflite_path.AppendASCII("safe_browsing") + .AppendASCII("visual_model_android.tflite"); #else - tflite_path = tflite_path.AppendASCII("safe_browsing") - .AppendASCII("visual_model_desktop.tflite"); + tflite_path = tflite_path.AppendASCII("safe_browsing") + .AppendASCII("visual_model_desktop.tflite"); #endif - base::File tflite_model(tflite_path, - base::File::FLAG_OPEN | base::File::FLAG_READ); - ASSERT_TRUE(tflite_model.IsValid()); + base::File tflite_model(tflite_path, + base::File::FLAG_OPEN | base::File::FLAG_READ); + ASSERT_TRUE(tflite_model.IsValid()); - base::FilePath model_file_path; - ASSERT_TRUE( - base::PathService::Get(chrome::DIR_TEST_DATA, &model_file_path)); - model_file_path = model_file_path.AppendASCII("safe_browsing") - .AppendASCII("client_model.pb"); + base::FilePath model_file_path; + ASSERT_TRUE( + base::PathService::Get(chrome::DIR_TEST_DATA, &model_file_path)); + model_file_path = model_file_path.AppendASCII("safe_browsing") + .AppendASCII("client_model.pb"); - csd_service->SetModelAndVisualTfLiteForTesting(model_file_path, - tflite_path); - run_loop.Run(); - } + csd_service->SetModelAndVisualTfLiteForTesting(model_file_path, + tflite_path); + run_loop.Run(); } // Check that the update was successful @@ -356,18 +274,13 @@ ClientPhishingRequest request; ASSERT_TRUE(request.ParseFromString(verdict)); - if (!base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - EXPECT_EQ(123, request.model_version()); - } else { - EXPECT_EQ(27, request.model_version()); - csd_service->ClassifyPhishingThroughThresholds(&request); + EXPECT_EQ(27, request.model_version()); + csd_service->ClassifyPhishingThroughThresholds(&request); - histogram_tester.ExpectUniqueSample( - "SBClientPhishing.ClassifyThresholdsResult", - safe_browsing::SBClientDetectionClassifyThresholdsResult::kSuccess, - 1); // Example model file version - } + histogram_tester.ExpectUniqueSample( + "SBClientPhishing.ClassifyThresholdsResult", + safe_browsing::SBClientDetectionClassifyThresholdsResult::kSuccess, + 1); // Example model file version } } @@ -379,7 +292,7 @@ #define MAYBE_TfLiteClassificationAfterTwoModelUploads \ TfLiteClassificationAfterTwoModelUploads #endif -IN_PROC_BROWSER_TEST_P(ClientSideDetectionServiceBrowserTest, +IN_PROC_BROWSER_TEST_F(ClientSideDetectionServiceBrowserTest, MAYBE_TfLiteClassificationAfterTwoModelUploads) { #if BUILDFLAG(IS_MAC) if (base::mac::IsAtLeastOS13()) { @@ -387,10 +300,6 @@ } #endif - if (!base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - return; - } GURL url(embedded_test_server()->GetURL("/empty.html")); ASSERT_TRUE(content::NavigateToURL(web_contents(), url)); @@ -505,19 +414,13 @@ ClientPhishingRequest request; ASSERT_TRUE(request.ParseFromString(verdict)); - if (!base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - EXPECT_EQ(123, request.model_version()); - } else { - EXPECT_EQ(27, request.model_version()); // Example model file version + EXPECT_EQ(27, request.model_version()); // Example model file version - csd_service->ClassifyPhishingThroughThresholds(&request); + csd_service->ClassifyPhishingThroughThresholds(&request); - histogram_tester.ExpectUniqueSample( - "SBClientPhishing.ClassifyThresholdsResult", - safe_browsing::SBClientDetectionClassifyThresholdsResult::kSuccess, - 1); - } + histogram_tester.ExpectUniqueSample( + "SBClientPhishing.ClassifyThresholdsResult", + safe_browsing::SBClientDetectionClassifyThresholdsResult::kSuccess, 1); } }
diff --git a/chrome/browser/safe_browsing/client_side_detection_service_factory.cc b/chrome/browser/safe_browsing/client_side_detection_service_factory.cc index 2966da06..1f5de2b 100644 --- a/chrome/browser/safe_browsing/client_side_detection_service_factory.cc +++ b/chrome/browser/safe_browsing/client_side_detection_service_factory.cc
@@ -56,9 +56,7 @@ auto* opt_guide = OptimizationGuideKeyedServiceFactory::GetForProfile( Profile::FromBrowserContext(context)); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide) && - !opt_guide) { + if (!opt_guide) { return nullptr; }
diff --git a/chrome/browser/safe_browsing/client_side_detection_service_unittest.cc b/chrome/browser/safe_browsing/client_side_detection_service_unittest.cc index 15087da0..77438b0 100644 --- a/chrome/browser/safe_browsing/client_side_detection_service_unittest.cc +++ b/chrome/browser/safe_browsing/client_side_detection_service_unittest.cc
@@ -106,7 +106,7 @@ class ClientSideDetectionServiceTest : public testing::Test, - public testing::WithParamInterface<std::tuple<bool, bool, bool>> { + public testing::WithParamInterface<std::tuple<bool, bool>> { public: ClientSideDetectionServiceTest() : profile_manager_(TestingBrowserProcess::GetGlobal()) { @@ -114,10 +114,6 @@ profile_ = profile_manager_.CreateTestingProfile("test-user"); std::vector<base::test::FeatureRefAndParams> enabled_features = { {kSafeBrowsingRemoveCookiesInAuthRequests, {}}}; - if (ShouldEnableCacao()) { - enabled_features.push_back( - {kClientSideDetectionModelOptimizationGuide, {}}); - } if (ShouldEnableESBDailyPhishingLimit()) { base::FieldTrialParams params; params["kMaxReportsPerIntervalESB"] = "10"; @@ -131,24 +127,20 @@ feature_list_.InitWithFeaturesAndParameters(enabled_features, {}); } - bool ShouldEnableCacao() { return get<0>(GetParam()); } - bool ShouldEnableESBDailyPhishingLimit() { return get<1>(GetParam()); } + bool ShouldEnableESBDailyPhishingLimit() { return get<0>(GetParam()); } - bool ShouldEnableImageEmbeddingModelCacao() { return get<2>(GetParam()); } + bool ShouldEnableImageEmbeddingModelCacao() { return get<1>(GetParam()); } protected: void SetUp() override { test_shared_loader_factory_ = base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( &test_url_loader_factory_); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - model_observer_tracker_ = - std::make_unique<ClientSidePhishingModelObserverTracker>(); - background_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner( - {base::MayBlock(), base::TaskPriority::BEST_EFFORT}); - } + model_observer_tracker_ = + std::make_unique<ClientSidePhishingModelObserverTracker>(); + background_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner( + {base::MayBlock(), base::TaskPriority::BEST_EFFORT}); } void TearDown() override { @@ -322,18 +314,13 @@ INSTANTIATE_TEST_SUITE_P(All, ClientSideDetectionServiceTest, - testing::Combine(testing::Bool(), - testing::Bool(), - testing::Bool())); + testing::Combine(testing::Bool(), testing::Bool())); TEST_P(ClientSideDetectionServiceTest, ServiceObjectDeletedBeforeCallbackDone) { csd_service_ = std::make_unique<ClientSideDetectionService>( std::make_unique<ChromeClientSideDetectionServiceDelegate>(profile_), model_observer_tracker_.get(), background_task_runner_); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - ReadModelAndTfLiteFiles(); - } + ReadModelAndTfLiteFiles(); profile_->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, true); EXPECT_NE(csd_service_.get(), nullptr); // We delete the client-side detection service class even though the callbacks @@ -348,10 +335,7 @@ csd_service_ = std::make_unique<ClientSideDetectionService>( std::make_unique<ChromeClientSideDetectionServiceDelegate>(profile_), model_observer_tracker_.get(), background_task_runner_); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { ReadModelAndTfLiteFiles(); - } csd_service_->SetURLLoaderFactoryForTesting(test_shared_loader_factory_); GURL url("http://a.com/"); @@ -411,10 +395,7 @@ csd_service_ = std::make_unique<ClientSideDetectionService>( std::make_unique<ChromeClientSideDetectionServiceDelegate>(profile_), model_observer_tracker_.get(), background_task_runner_); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { ReadModelAndTfLiteFiles(); - } csd_service_->SetURLLoaderFactoryForTesting(test_shared_loader_factory_); profile_->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, true); @@ -443,10 +424,7 @@ csd_service_ = std::make_unique<ClientSideDetectionService>( std::make_unique<ChromeClientSideDetectionServiceDelegate>(profile_), model_observer_tracker_.get(), background_task_runner_); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { ReadModelAndTfLiteFiles(); - } csd_service_->SetURLLoaderFactoryForTesting(test_shared_loader_factory_); profile_->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, true); @@ -473,10 +451,7 @@ csd_service_ = std::make_unique<ClientSideDetectionService>( std::make_unique<ChromeClientSideDetectionServiceDelegate>(profile_), model_observer_tracker_.get(), background_task_runner_); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { ReadModelAndTfLiteFiles(); - } base::Time now = base::Time::Now(); base::TimeDelta twenty_five_hours = base::Hours(25); @@ -493,10 +468,7 @@ csd_service_ = std::make_unique<ClientSideDetectionService>( std::make_unique<ChromeClientSideDetectionServiceDelegate>(profile_), model_observer_tracker_.get(), background_task_runner_); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { ReadModelAndTfLiteFiles(); - } profile_->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnhanced, true); @@ -524,10 +496,7 @@ csd_service_ = std::make_unique<ClientSideDetectionService>( std::make_unique<ChromeClientSideDetectionServiceDelegate>(profile_), model_observer_tracker_.get(), background_task_runner_); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { ReadModelAndTfLiteFiles(); - } TestCache(); } @@ -614,9 +583,7 @@ TEST_P(ClientSideDetectionServiceTest, TestReceivingImageEmbedderUpdatesAfterResubscription) { - if (!(base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide) && - base::FeatureList::IsEnabled(kClientSideDetectionModelImageEmbedder))) { + if (!base::FeatureList::IsEnabled(kClientSideDetectionModelImageEmbedder)) { return; }
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc index 5417f25..fdd8526a 100644 --- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc +++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
@@ -129,7 +129,6 @@ // Delayed warnings feature checks if the Suspicious Site Reporter extension // is installed. These includes are to fake-install this extension. #include "chrome/browser/extensions/crx_installer.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/test_extension_registry_observer.h" #include "extensions/common/extension.h" #endif
diff --git a/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service_unittest.cc b/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service_unittest.cc index 18f33ef..f50aa73 100644 --- a/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service_unittest.cc +++ b/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service_unittest.cc
@@ -7,6 +7,7 @@ #include "base/files/scoped_temp_dir.h" #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "chrome/browser/prefs/browser_prefs.h" #include "chrome/browser/profiles/profile.h" @@ -24,6 +25,7 @@ #include "components/prefs/pref_registry_simple.h" #include "components/prefs/testing_pref_service.h" #include "components/prefs/testing_pref_store.h" +#include "components/safe_browsing/core/browser/tailored_security_service/tailored_security_notification_result.h" #include "components/safe_browsing/core/common/features.h" #include "components/safe_browsing/core/common/safe_browsing_prefs.h" #include "components/sync/test/test_sync_service.h" @@ -93,6 +95,8 @@ }; } // namespace +// TODO(crbug.com/1473470): Move tests related to base class behavior of +// MaybeNotifySyncUser to the test suite for TailoredSecurityService. class ChromeTailoredSecurityServiceTest : public testing::Test { public: ChromeTailoredSecurityServiceTest() @@ -193,6 +197,11 @@ TestingProfile* profile() { return profile_; } + syncer::TestSyncService* sync_service() { + return static_cast<syncer::TestSyncService*>( + SyncServiceFactory::GetForProfile(profile())); + } + signin::IdentityTestEnvironment* GetIdentityTestEnv() { DCHECK(identity_test_env_adaptor_); return identity_test_env_adaptor_->identity_test_env(); @@ -246,6 +255,63 @@ EXPECT_TRUE(tailored_security_service()->previous_show_enable_dialog_value()); } +TEST_F(ChromeTailoredSecurityServiceTest, + TailoredSecurityEnabledButHistorySyncDisabledDoesNotShowEnableDialog) { + SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION); + const GURL google_url("https://www.google.com"); + AddTab(google_url); + int initial_times_displayed = + tailored_security_service()->times_dialog_displayed(); + + // disable history sync + sync_service()->GetUserSettings()->SetSelectedTypes( + /*sync_everything=*/false, + /*types=*/{}); + tailored_security_service()->MaybeNotifySyncUser(kTailoredSecurityEnabled, + base::Time::Now()); + + EXPECT_EQ(tailored_security_service()->times_dialog_displayed(), + initial_times_displayed); +} + +TEST_F(ChromeTailoredSecurityServiceTest, + TailoredSecurityEnabledButHistorySyncDisabledLogsHistoryNotSynced) { + SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION); + const GURL google_url("https://www.google.com"); + AddTab(google_url); + + // disable history sync + sync_service()->GetUserSettings()->SetSelectedTypes( + /*sync_everything=*/false, + /*types=*/{}); + base::HistogramTester tester; + tailored_security_service()->MaybeNotifySyncUser(kTailoredSecurityEnabled, + base::Time::Now()); + + tester.ExpectBucketCount( + "SafeBrowsing.TailoredSecurity.SyncPromptEnabledNotificationResult2", + TailoredSecurityNotificationResult::kHistoryNotSynced, 1); +} + +TEST_F(ChromeTailoredSecurityServiceTest, + TailoredSecurityEnabledButHistorySyncEnabledDoesNotLogHistoryNotSynced) { + SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION); + const GURL google_url("https://www.google.com"); + AddTab(google_url); + + // enable history sync + sync_service()->GetUserSettings()->SetSelectedTypes( + /*sync_everything=*/false, + /*types=*/{syncer::UserSelectableType::kHistory}); + base::HistogramTester tester; + tailored_security_service()->MaybeNotifySyncUser(kTailoredSecurityEnabled, + base::Time::Now()); + + tester.ExpectBucketCount( + "SafeBrowsing.TailoredSecurity.SyncPromptEnabledNotificationResult2", + TailoredSecurityNotificationResult::kHistoryNotSynced, 0); +} + TEST_F(ChromeTailoredSecurityServiceTest, TsEnabledEnablesEp) { SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION); const GURL google_url("https://www.google.com");
diff --git a/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer_unittest.cc b/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer_unittest.cc index 084e638..fc50aeb 100644 --- a/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer_unittest.cc +++ b/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer_unittest.cc
@@ -44,7 +44,7 @@ auto sync_service = std::make_unique<syncer::TestSyncService>(); sync_service->GetUserSettings()->SetSelectedType( - syncer::UserSelectableType::kPreferences, false); + syncer::UserSelectableType::kHistory, false); return sync_service; })}}; }
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc index 61622ab..f45c339 100644 --- a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc +++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
@@ -113,9 +113,6 @@ } if (item->GetState() == download::DownloadItem::COMPLETE) { - base::UmaHistogramBoolean( - "SafeBrowsing.AndroidTelemetry.ApkDownload.IsMimeTypeApk", - (item->GetMimeType() == kApkMimeType)); // Download completed. Send report. std::unique_ptr<ClientSafeBrowsingReportRequest> report = GetReport(item); MaybeSendApkDownloadReport(
diff --git a/chrome/browser/send_tab_to_self/send_tab_to_self_util.cc b/chrome/browser/send_tab_to_self/send_tab_to_self_util.cc index 9ed69163..1d96856 100644 --- a/chrome/browser/send_tab_to_self/send_tab_to_self_util.cc +++ b/chrome/browser/send_tab_to_self/send_tab_to_self_util.cc
@@ -25,13 +25,12 @@ if (!web_contents) return absl::nullopt; - auto* profile = - Profile::FromBrowserContext(web_contents->GetBrowserContext()); - return GetEntryPointDisplayReason( - web_contents->GetLastCommittedURL(), - SyncServiceFactory::GetForProfile(profile), - SendTabToSelfSyncServiceFactory::GetForProfile(profile), - profile->GetPrefs()); + send_tab_to_self::SendTabToSelfSyncService* service = + SendTabToSelfSyncServiceFactory::GetForProfile( + Profile::FromBrowserContext(web_contents->GetBrowserContext())); + return service ? service->GetEntryPointDisplayReason( + web_contents->GetLastCommittedURL()) + : absl::nullopt; } bool ShouldDisplayEntryPoint(content::WebContents* web_contents) {
diff --git a/chrome/browser/send_tab_to_self/send_tab_to_self_util_unittest.cc b/chrome/browser/send_tab_to_self/send_tab_to_self_util_unittest.cc index 3e591688..7e2b9089 100644 --- a/chrome/browser/send_tab_to_self/send_tab_to_self_util_unittest.cc +++ b/chrome/browser/send_tab_to_self/send_tab_to_self_util_unittest.cc
@@ -5,19 +5,14 @@ #include "chrome/browser/send_tab_to_self/send_tab_to_self_util.h" #include <memory> -#include <utility> #include "base/functional/bind.h" -#include "base/test/scoped_feature_list.h" #include "chrome/browser/sync/send_tab_to_self_sync_service_factory.h" -#include "chrome/browser/sync/sync_service_factory.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/test/base/browser_with_test_window_test.h" -#include "components/send_tab_to_self/features.h" #include "components/send_tab_to_self/send_tab_to_self_sync_service.h" #include "components/send_tab_to_self/test_send_tab_to_self_model.h" -#include "components/sync/test/test_sync_service.h" #include "content/public/test/navigation_simulator.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" @@ -29,36 +24,16 @@ const char kHttpsUrl[] = "https://www.foo.com"; const char kHttpsUrl2[] = "https://www.bar.com"; -class FakeSendTabToSelfModel : public TestSendTabToSelfModel { - public: - FakeSendTabToSelfModel() = default; - ~FakeSendTabToSelfModel() override = default; - - void SetIsReady(bool is_ready) { is_ready_ = is_ready; } - void SetHasValidTargetDevice(bool has_valid_target_device) { - if (has_valid_target_device) { - DCHECK(is_ready_) << "Target devices are only known if the model's ready"; - } - has_valid_target_device_ = has_valid_target_device; - } - - bool IsReady() override { return is_ready_; } - bool HasValidTargetDevice() override { return has_valid_target_device_; } - - private: - bool is_ready_ = false; - bool has_valid_target_device_ = false; -}; - +// A fake that's always ready to offer send-tab-to-self. class FakeSendTabToSelfSyncService : public SendTabToSelfSyncService { public: FakeSendTabToSelfSyncService() = default; ~FakeSendTabToSelfSyncService() override = default; - FakeSendTabToSelfModel* GetSendTabToSelfModel() override { return &model_; } - - private: - FakeSendTabToSelfModel model_; + absl::optional<EntryPointDisplayReason> GetEntryPointDisplayReason( + const GURL&) override { + return EntryPointDisplayReason::kOfferFeature; + } }; std::unique_ptr<KeyedService> BuildFakeSendTabToSelfSyncService( @@ -66,10 +41,6 @@ return std::make_unique<FakeSendTabToSelfSyncService>(); } -std::unique_ptr<KeyedService> BuildTestSyncService(content::BrowserContext*) { - return std::make_unique<syncer::TestSyncService>(); -} - class SendTabToSelfUtilTest : public BrowserWithTestWindowTest { public: void SetUp() override { @@ -80,35 +51,15 @@ TestingProfile::TestingFactories GetTestingFactories() override { return {{SendTabToSelfSyncServiceFactory::GetInstance(), - base::BindRepeating(&BuildFakeSendTabToSelfSyncService)}, - {SyncServiceFactory::GetInstance(), - base::BindRepeating(&BuildTestSyncService)}}; + base::BindRepeating(&BuildFakeSendTabToSelfSyncService)}}; } content::WebContents* web_contents() { return browser()->tab_strip_model()->GetActiveWebContents(); } - - FakeSendTabToSelfSyncService* service() { - return static_cast<FakeSendTabToSelfSyncService*>( - SendTabToSelfSyncServiceFactory::GetForProfile(profile())); - } - - void SignIn() { - CoreAccountInfo account; - account.gaia = "gaia_id"; - account.email = "email@test.com"; - account.account_id = CoreAccountId::FromGaiaId(account.gaia); - static_cast<syncer::TestSyncService*>( - SyncServiceFactory::GetForProfile(profile())) - ->SetAccountInfo(account); - } }; TEST_F(SendTabToSelfUtilTest, ShouldHideEntryPointInOmniboxWhileNavigating) { - SignIn(); - service()->GetSendTabToSelfModel()->SetIsReady(true); - service()->GetSendTabToSelfModel()->SetHasValidTargetDevice(true); NavigateAndCommitActiveTab(GURL(kHttpsUrl)); ASSERT_FALSE(web_contents()->IsWaitingForResponse());
diff --git a/chrome/browser/sessions/better_session_restore_browsertest.cc b/chrome/browser/sessions/better_session_restore_browsertest.cc index db02b672..86101cd 100644 --- a/chrome/browser/sessions/better_session_restore_browsertest.cc +++ b/chrome/browser/sessions/better_session_restore_browsertest.cc
@@ -62,7 +62,7 @@ #include "services/network/test/test_utils.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif #if BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/sessions/session_restore_browsertest.cc b/chrome/browser/sessions/session_restore_browsertest.cc index 6bef43f..6f99ff03 100644 --- a/chrome/browser/sessions/session_restore_browsertest.cc +++ b/chrome/browser/sessions/session_restore_browsertest.cc
@@ -118,7 +118,7 @@ #include "ui/gfx/color_palette.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif #if defined(USE_AURA)
diff --git a/chrome/browser/sync/send_tab_to_self_sync_service_factory.cc b/chrome/browser/sync/send_tab_to_self_sync_service_factory.cc index b4bad4ac..9edf7ed 100644 --- a/chrome/browser/sync/send_tab_to_self_sync_service_factory.cc +++ b/chrome/browser/sync/send_tab_to_self_sync_service_factory.cc
@@ -62,5 +62,5 @@ return new send_tab_to_self::SendTabToSelfSyncService( chrome::GetChannel(), std::move(store_factory), history_service, - device_info_tracker); + profile->GetPrefs(), device_info_tracker); }
diff --git a/chrome/browser/sync/sync_service_factory.cc b/chrome/browser/sync/sync_service_factory.cc index b86e3e2..a62f5ac0 100644 --- a/chrome/browser/sync/sync_service_factory.cc +++ b/chrome/browser/sync/sync_service_factory.cc
@@ -50,6 +50,7 @@ #include "chrome/common/buildflags.h" #include "chrome/common/channel_info.h" #include "components/network_time/network_time_tracker.h" +#include "components/send_tab_to_self/send_tab_to_self_sync_service.h" #include "components/supervised_user/core/common/buildflags.h" #include "components/sync/base/command_line_switches.h" #include "components/sync/service/sync_service_impl.h" @@ -182,6 +183,9 @@ password_store->OnSyncServiceInitialized(sync_service.get()); } + SendTabToSelfSyncServiceFactory::GetForProfile(profile) + ->OnSyncServiceInitialized(sync_service.get()); + // Allow sync_preferences/ components to use SyncService. sync_preferences::PrefServiceSyncable* pref_service = PrefServiceSyncableFromProfile(profile);
diff --git a/chrome/browser/themes/theme_service.cc b/chrome/browser/themes/theme_service.cc index bd7eba5b..e1a817c 100644 --- a/chrome/browser/themes/theme_service.cc +++ b/chrome/browser/themes/theme_service.cc
@@ -16,7 +16,6 @@ #include "base/location.h" #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted_memory.h" -#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/user_metrics.h" #include "base/observer_list.h" @@ -100,9 +99,7 @@ if (g_dont_write_theme_pack_for_testing) return; - const bool success = - pack->WriteToDisk(directory.Append(chrome::kThemePackFilename)); - base::UmaHistogramBoolean("Browser.ThemeService.WritePackToDisk", success); + pack->WriteToDisk(directory.Append(chrome::kThemePackFilename)); } } // namespace
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index e8e4ad69..ec3bec4 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -1596,6 +1596,8 @@ "webui/feedback/feedback_handler.h", "webui/feedback/feedback_ui.cc", "webui/feedback/feedback_ui.h", + "webui/hats/hats_ui.cc", + "webui/hats/hats_ui.h", "webui/help/version_updater.h", "webui/history/browsing_history_handler.cc", "webui/history/browsing_history_handler.h", @@ -3688,6 +3690,7 @@ "//chromeos/constants", "//chromeos/dbus/power", "//chromeos/strings", + "//chromeos/version", "//components/account_manager_core:account_manager_core", "//components/metrics/structured:structured_events", "//ui/chromeos/styles:cros_styles_views",
diff --git a/chrome/browser/ui/ash/touch_selection_menu_chromeos.cc b/chrome/browser/ui/ash/touch_selection_menu_chromeos.cc index dbccaee..f915f812 100644 --- a/chrome/browser/ui/ash/touch_selection_menu_chromeos.cc +++ b/chrome/browser/ui/ash/touch_selection_menu_chromeos.cc
@@ -17,6 +17,7 @@ #include "ui/display/screen.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia_operations.h" +#include "ui/touch_selection/touch_selection_metrics.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/button/label_button.h" @@ -80,6 +81,7 @@ TouchSelectionMenuChromeOS::~TouchSelectionMenuChromeOS() = default; void TouchSelectionMenuChromeOS::ActionButtonPressed() { + ui::RecordTouchSelectionMenuSmartAction(); auto* arc_service_manager = arc::ArcServiceManager::Get(); if (!arc_service_manager) return;
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc index 32917a7..54bfbc77 100644 --- a/chrome/browser/ui/autofill/chrome_autofill_client.cc +++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -239,10 +239,6 @@ } MerchantPromoCodeManager* ChromeAutofillClient::GetMerchantPromoCodeManager() { - if (!base::FeatureList::IsEnabled( - features::kAutofillFillMerchantPromoCodeFields)) { - return nullptr; - } Profile* profile = Profile::FromBrowserContext(web_contents()->GetBrowserContext()); return MerchantPromoCodeManagerFactory::GetForProfile(profile);
diff --git a/chrome/browser/ui/autofill/payments/offer_notification_bubble_controller_impl.cc b/chrome/browser/ui/autofill/payments/offer_notification_bubble_controller_impl.cc index f0bcfd6..f42afcb 100644 --- a/chrome/browser/ui/autofill/payments/offer_notification_bubble_controller_impl.cc +++ b/chrome/browser/ui/autofill/payments/offer_notification_bubble_controller_impl.cc
@@ -71,10 +71,7 @@ IDS_AUTOFILL_CARD_LINKED_OFFER_REMINDER_TITLE); case AutofillOfferData::OfferType::GPAY_PROMO_CODE_OFFER: return l10n_util::GetStringUTF16( - base::FeatureList::IsEnabled( - features::kAutofillFillMerchantPromoCodeFields) - ? IDS_AUTOFILL_GPAY_PROMO_CODE_OFFERS_REMINDER_TITLE - : IDS_AUTOFILL_PROMO_CODE_OFFERS_REMINDER_TITLE); + IDS_AUTOFILL_GPAY_PROMO_CODE_OFFERS_REMINDER_TITLE); case AutofillOfferData::OfferType::FREE_LISTING_COUPON_OFFER: return l10n_util::GetStringUTF16( IDS_AUTOFILL_PROMO_CODE_OFFERS_REMINDER_TITLE);
diff --git a/chrome/browser/ui/autofill/payments/offer_notification_bubble_controller_impl_unittest.cc b/chrome/browser/ui/autofill/payments/offer_notification_bubble_controller_impl_unittest.cc index 33f879a..7940f90 100644 --- a/chrome/browser/ui/autofill/payments/offer_notification_bubble_controller_impl_unittest.cc +++ b/chrome/browser/ui/autofill/payments/offer_notification_bubble_controller_impl_unittest.cc
@@ -71,8 +71,6 @@ } protected: - base::test::ScopedFeatureList feature_list_; - class MockCouponService : public CouponService { public: MOCK_METHOD(void, @@ -280,8 +278,6 @@ // Tests that the offer notification bubble will be shown, and coupon service // will not be called for a GPay promo code offer. TEST_F(OfferNotificationBubbleControllerImplTest, GPayPromoCode_BubbleShown) { - feature_list_.InitAndEnableFeature( - autofill::features::kAutofillFillMerchantPromoCodeFields); AutofillOfferData offer = CreateTestGPayPromoCodeOffer( /*merchant_origins=*/{GURL("https://www.example.com/first/") .DeprecatedGetOriginAsURL()}, @@ -295,24 +291,4 @@ IDS_AUTOFILL_GPAY_PROMO_CODE_OFFERS_REMINDER_TITLE)); } -// Tests that the offer notification bubble will be shown as a free-listing -// coupon notification bubble when the feature is disabled. -TEST_F(OfferNotificationBubbleControllerImplTest, - GPayPromoCode_BubbleShownWithFLCTitle) { - feature_list_.InitAndDisableFeature( - autofill::features::kAutofillFillMerchantPromoCodeFields); - - AutofillOfferData offer = CreateTestGPayPromoCodeOffer( - /*merchant_origins=*/{GURL("https://www.example.com/first/") - .DeprecatedGetOriginAsURL()}, - /*promo_code=*/"FREEFALL5678"); - ShowBubble(&offer); - - EXPECT_CALL(mock_coupon_service_, GetCouponDisplayTimestamp).Times(0); - EXPECT_TRUE(controller()->GetOfferNotificationBubbleView()); - EXPECT_EQ( - controller()->GetWindowTitle(), - l10n_util::GetStringUTF16(IDS_AUTOFILL_PROMO_CODE_OFFERS_REMINDER_TITLE)); -} - } // namespace autofill
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc index d639b73..2c98bdd6 100644 --- a/chrome/browser/ui/browser.cc +++ b/chrome/browser/ui/browser.cc
@@ -770,10 +770,8 @@ // |contents| can be NULL because GetWindowTitleForCurrentTab is called by the // window during the window's creation (before tabs have been added). if (title.empty() && contents) { - title = FormatTitleForDisplay(app_controller_ && - !app_controller_->has_tab_strip() - ? app_controller_->GetTitle() - : contents->GetTitle()); + title = FormatTitleForDisplay(app_controller_ ? app_controller_->GetTitle() + : contents->GetTitle()); #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) // If the app name is requested and this is a captive portal window, the // title should indicate that this is a captive portal window. Captive
diff --git a/chrome/browser/ui/browser_browsertest.cc b/chrome/browser/ui/browser_browsertest.cc index 9e0bbf9..6c842a0 100644 --- a/chrome/browser/ui/browser_browsertest.cc +++ b/chrome/browser/ui/browser_browsertest.cc
@@ -136,7 +136,7 @@ #include "ui/base/ui_base_features.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "chrome/browser/ui/cocoa/test/run_loop_testing.h" #include "ui/accelerated_widget_mac/ca_transaction_observer.h" #include "ui/base/test/scoped_fake_nswindow_fullscreen.h"
diff --git a/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.mm b/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.mm index fc2a6757..0b186fd9a 100644 --- a/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.mm +++ b/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.mm
@@ -4,8 +4,8 @@ #import "chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/containers/adapters.h" -#include "base/mac/scoped_nsautorelease_pool.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "chrome/app/chrome_command_ids.h" @@ -414,7 +414,7 @@ // must be drained before the window finishes -dealloc. In this method, an // autorelease is sent by the invocation of [NSApp windows]. // http://crbug.com/406944. - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; NSString* name = [notification name]; if ([name isEqualToString:NSWindowDidBecomeMainNotification]) {
diff --git a/chrome/browser/ui/hats/DIR_METADATA b/chrome/browser/ui/hats/DIR_METADATA new file mode 100644 index 0000000..4b06f183 --- /dev/null +++ b/chrome/browser/ui/hats/DIR_METADATA
@@ -0,0 +1,3 @@ +monorail { + component: "UI>Browser>HaTS" +}
diff --git a/chrome/browser/ui/media_router/cast_notification_controller_lacros.cc b/chrome/browser/ui/media_router/cast_notification_controller_lacros.cc index 1ed5b4e..a8df7a0 100644 --- a/chrome/browser/ui/media_router/cast_notification_controller_lacros.cc +++ b/chrome/browser/ui/media_router/cast_notification_controller_lacros.cc
@@ -140,6 +140,11 @@ message_center::RichNotificationData data; data.buttons = GetButtons(route, freeze_host); data.pinned = true; + // `vector_small_image` is ignored by the crosapi so we must convert it to + // `small_image`. Also, kAppIconImageSize=16 is used in AshNotificationView, + // but 16 here somehow results in a blurry image. + data.small_image = gfx::Image(gfx::CreateVectorIcon( + gfx::IconDescription(vector_icons::kMediaRouterIdleIcon, 32))); return message_center::Notification{ message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE,
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc index 95c8c85..5aa67c3a 100644 --- a/chrome/browser/ui/ui_features.cc +++ b/chrome/browser/ui/ui_features.cc
@@ -220,10 +220,6 @@ const char kTabHoverCardAdditionalMaxWidthDelay[] = "additional_max_width_delay"; -BASE_FEATURE(kTabOrganization, - "TabOrganization", - base::FEATURE_DISABLED_BY_DEFAULT); - BASE_FEATURE(kTabSearchChevronIcon, "TabSearchChevronIcon", base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h index 51bd916..457335a8 100644 --- a/chrome/browser/ui/ui_features.h +++ b/chrome/browser/ui/ui_features.h
@@ -127,8 +127,6 @@ // typically when there are less than 5 or 6 tabs in a browser window. extern const char kTabHoverCardAdditionalMaxWidthDelay[]; -BASE_DECLARE_FEATURE(kTabOrganization); - BASE_DECLARE_FEATURE(kTabSearchChevronIcon); BASE_DECLARE_FEATURE(kTabSearchFeedback);
diff --git a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.cc index 27bfb64..6b193bd 100644 --- a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.cc +++ b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views.cc
@@ -62,12 +62,7 @@ InitWithCardLinkedOfferContent(); return; case AutofillOfferData::OfferType::GPAY_PROMO_CODE_OFFER: - if (base::FeatureList::IsEnabled( - features::kAutofillFillMerchantPromoCodeFields)) { - InitWithGPayPromoCodeOfferContent(); - } else { - InitWithFreeListingCouponOfferContent(); - } + InitWithGPayPromoCodeOfferContent(); return; case AutofillOfferData::OfferType::FREE_LISTING_COUPON_OFFER: InitWithFreeListingCouponOfferContent(); @@ -82,13 +77,7 @@ GetWindowTitle(), TitleWithIconAndSeparatorView::Icon::GOOGLE_G)); // Set the header image for free listing coupon notification bubble. - // If the promo code field autofill feature is off, we also need to add this - // image for GPay promo code offer because we are showing the free listing - // coupon bubble in this case. - if (controller_->GetOffer()->IsFreeListingCouponOffer() || - (!base::FeatureList::IsEnabled( - features::kAutofillFillMerchantPromoCodeFields) && - controller_->GetOffer()->IsGPayPromoCodeOffer())) { + if (controller_->GetOffer()->IsFreeListingCouponOffer()) { ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); auto* autofill_offers_banner = bundle.GetImageSkiaNamed(IDR_AUTOFILL_OFFERS);
diff --git a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.cc b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.cc index a8b3cbb9..e8504788 100644 --- a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.cc +++ b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.cc
@@ -43,16 +43,14 @@ /*enabled_features=*/ {{commerce::kRetailCoupons, {{commerce::kRetailCouponsWithCodeParam, "true"}}}, - {features::kAutofillEnableOfferNotificationForPromoCodes, {}}, - {features::kAutofillFillMerchantPromoCodeFields, {}}}, + {features::kAutofillEnableOfferNotificationForPromoCodes, {}}}, /*disabled_features=*/{}); } else { scoped_feature_list_.InitWithFeatures( /*enabled_features=*/{}, /*disabled_features=*/{ commerce::kRetailCoupons, - features::kAutofillEnableOfferNotificationForPromoCodes, - features::kAutofillFillMerchantPromoCodeFields}); + features::kAutofillEnableOfferNotificationForPromoCodes}); } }
diff --git a/chrome/browser/ui/views/create_application_shortcut_view_test_support.cc b/chrome/browser/ui/views/create_application_shortcut_view_test_support.cc new file mode 100644 index 0000000..e762bde71 --- /dev/null +++ b/chrome/browser/ui/views/create_application_shortcut_view_test_support.cc
@@ -0,0 +1,38 @@ +// Copyright 2023 The Chromium Authors +// 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/create_application_shortcut_view_test_support.h" + +#include "base/callback_list.h" +#include "base/check.h" +#include "base/run_loop.h" +#include "ui/base/ui_base_types.h" +#include "ui/views/test/dialog_test.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" +#include "ui/views/window/dialog_delegate.h" + +CreateChromeApplicationShortcutViewWaiter:: + CreateChromeApplicationShortcutViewWaiter() + : waiter_(views::test::AnyWidgetTestPasskey{}, + "CreateChromeApplicationShortcutView") {} + +void CreateChromeApplicationShortcutViewWaiter::WaitForAndAccept() && { + views::Widget* widget = waiter_.WaitIfNeededAndGet(); + CHECK(widget != nullptr); + + // Wait until the "ok" button is enabled (which in practice corresponds to + // waiting until `CreateChromeApplicationShortcutView::OnAppInfoLoaded` is + // called). + views::View* ok_button = + widget->widget_delegate()->AsDialogDelegate()->GetOkButton(); + while (!ok_button->GetEnabled()) { + base::RunLoop run_loop; + base::CallbackListSubscription subscription = + ok_button->AddEnabledChangedCallback(run_loop.QuitClosure()); + run_loop.Run(); + } + + views::test::AcceptDialog(widget); +}
diff --git a/chrome/browser/ui/views/create_application_shortcut_view_test_support.h b/chrome/browser/ui/views/create_application_shortcut_view_test_support.h new file mode 100644 index 0000000..d1be88f --- /dev/null +++ b/chrome/browser/ui/views/create_application_shortcut_view_test_support.h
@@ -0,0 +1,31 @@ +// Copyright 2023 The Chromium Authors +// 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_CREATE_APPLICATION_SHORTCUT_VIEW_TEST_SUPPORT_H_ +#define CHROME_BROWSER_UI_VIEWS_CREATE_APPLICATION_SHORTCUT_VIEW_TEST_SUPPORT_H_ + +#include "ui/views/widget/any_widget_observer.h" + +// A specialization of `NamedWidgetShownWaiter` for waiting for + working with +// the "CreateChromeApplicationShortcut" dialog. The specialization is +// desirable mostly because `NamedWidgetShownWaiter::WaitIfNeededAndGet` is not +// sufficient in this case, because we also need to wait until the dialog +// fetches the app info and enables the "ok" button. +// +// As with `NamedWidgetShownWaiter` it is important that +// `CreateChromeApplicationShortcutViewWaiter` be constructed before any code +// that might show the dialog. +class CreateChromeApplicationShortcutViewWaiter { + public: + CreateChromeApplicationShortcutViewWaiter(); + + // Waits 1) until the "CreateChromeApplicationShortcut" dialog is shown *and* + // 2) until the dialog can be accepted and *then* 3) accepts the dialog. + void WaitForAndAccept() &&; + + private: + views::NamedWidgetShownWaiter waiter_; +}; + +#endif // CHROME_BROWSER_UI_VIEWS_CREATE_APPLICATION_SHORTCUT_VIEW_TEST_SUPPORT_H_
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.cc index 56edb350..5e99f9a7 100644 --- a/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.cc +++ b/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.cc
@@ -17,7 +17,6 @@ #include "components/crx_file/id_util.h" #include "content/public/browser/notification_service.h" #include "content/public/test/test_utils.h" -#include "extensions/browser/notification_types.h" #include "extensions/common/extension.h" #include "extensions/common/extension_builder.h" #include "extensions/common/mojom/manifest.mojom-shared.h"
diff --git a/chrome/browser/ui/views/frame/browser_caption_button_container_win.cc b/chrome/browser/ui/views/frame/browser_caption_button_container_win.cc index d32ae58..a8cc711d 100644 --- a/chrome/browser/ui/views/frame/browser_caption_button_container_win.cc +++ b/chrome/browser/ui/views/frame/browser_caption_button_container_win.cc
@@ -79,7 +79,7 @@ auto* const layout = SetLayoutManager(std::make_unique<views::FlexLayout>()); layout->SetOrientation(views::LayoutOrientation::kHorizontal) .SetMainAxisAlignment(views::LayoutAlignment::kEnd) - .SetCrossAxisAlignment(views::LayoutAlignment::kStart) + .SetCrossAxisAlignment(views::LayoutAlignment::kStretch) .SetDefault( views::kFlexBehaviorKey, views::FlexSpecification(views::LayoutOrientation::kHorizontal,
diff --git a/chrome/browser/ui/views/frame/browser_frame_view_win.cc b/chrome/browser/ui/views/frame/browser_frame_view_win.cc index 49033954..a4c7824a 100644 --- a/chrome/browser/ui/views/frame/browser_frame_view_win.cc +++ b/chrome/browser/ui/views/frame/browser_frame_view_win.cc
@@ -585,7 +585,8 @@ int BrowserFrameViewWin::GetFrameHeight() const { if (browser_view()->GetTabStripVisible()) { - return browser_view()->tab_strip_region_view()->GetMinimumSize().height(); + return browser_view()->tab_strip_region_view()->GetMinimumSize().height() - + WindowTopY() - GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP); } return IsMaximized() ? TitlebarMaximizedVisualHeight() : TitlebarHeight(false);
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc b/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc index e069943..1b4ce9c4 100644 --- a/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc +++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
@@ -553,7 +553,7 @@ auto live_translate_image = std::make_unique<views::ImageView>(); live_translate_image->SetImage(ui::ImageModel::FromVectorIcon( - kUnbrandedTranslateIcon, ui::kColorIcon, kImageWidthDip)); + kTranslateChromeRefreshIcon, ui::kColorIcon, kImageWidthDip)); live_translate_container->AddChildView(std::move(live_translate_image)); auto live_translate_label_wrapper = std::make_unique<View>();
diff --git a/chrome/browser/ui/views/side_panel/side_panel_rounded_corner.cc b/chrome/browser/ui/views/side_panel/side_panel_rounded_corner.cc index f27a9003..5c0a254 100644 --- a/chrome/browser/ui/views/side_panel/side_panel_rounded_corner.cc +++ b/chrome/browser/ui/views/side_panel/side_panel_rounded_corner.cc
@@ -20,11 +20,8 @@ void SidePanelRoundedCorner::Layout() { views::View::Layout(); - const bool side_panel_visible = - browser_view_->unified_side_panel() && - browser_view_->unified_side_panel()->GetVisible(); SkPath path; - if (features::IsChromeRefresh2023() && side_panel_visible) { + if (features::IsChromeRefresh2023() && browser_view_->unified_side_panel()) { bool is_right_aligned = browser_view_->unified_side_panel()->IsRightAligned(); const float corner_radius = GetLayoutProvider()->GetCornerRadiusMetric(
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc index e9d2612c..cb15ec3 100644 --- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc +++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
@@ -7,6 +7,7 @@ #include <codecvt> #include <ostream> #include <string> +#include <utility> #include "base/command_line.h" #include "base/containers/contains.h" @@ -53,6 +54,7 @@ #include "chrome/browser/ui/startup/startup_browser_creator.h" #include "chrome/browser/ui/startup/web_app_startup_utils.h" #include "chrome/browser/ui/ui_features.h" +#include "chrome/browser/ui/views/create_application_shortcut_view_test_support.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/views/frame/browser_view_layout.h" #include "chrome/browser/ui/views/frame/toolbar_button_provider.h" @@ -1835,14 +1837,11 @@ app_home_page_handler.CreateAppShortcut(app_id, shortcuts_future.GetCallback()); #else // !BUILDFLAG(IS_MAC) - views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{}, - "CreateChromeApplicationShortcutView"); + CreateChromeApplicationShortcutViewWaiter waiter; app_home_page_handler.CreateAppShortcut(app_id, shortcuts_future.GetCallback()); FlushShortcutTasks(); - views::Widget* widget = waiter.WaitIfNeededAndGet(); - ASSERT_TRUE(widget != nullptr); - views::test::AcceptDialog(widget); + std::move(waiter).WaitForAndAccept(); #endif // BUILDFLAG(IS_MAC) EXPECT_TRUE(shortcuts_future.Wait()); AfterStateChangeAction();
diff --git a/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc index fee9cb5..7447dc7b 100644 --- a/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc +++ b/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc
@@ -1158,35 +1158,4 @@ EXPECT_EQ(tab_strip->active_index(), 0); } -// Tests the page title, which is used for accessibility. -IN_PROC_BROWSER_TEST_F(WebAppTabStripBrowserTest, PageTitle) { - GURL start_url = - embedded_test_server()->GetURL("/web_apps/tab_strip_customizations.html"); - AppId app_id = InstallWebAppFromPage(browser(), start_url); - Browser* app_browser = FindWebAppBrowser(browser()->profile(), app_id); - TabStripModel* tab_strip = app_browser->tab_strip_model(); - - EXPECT_TRUE(registrar().IsTabbedWindowModeEnabled(app_id)); - - // Expect app opened with pinned home tab. - EXPECT_EQ(tab_strip->count(), 1); - EXPECT_TRUE(tab_strip->IsTabPinned(0)); - EXPECT_EQ(tab_strip->GetWebContentsAt(0)->GetVisibleURL(), start_url); - EXPECT_EQ(tab_strip->active_index(), 0); - - EXPECT_EQ(app_browser->GetWindowTitleFromWebContents( - false, tab_strip->GetWebContentsAt(0)), - u"Tab Strip Customizations"); - - chrome::NewTab(app_browser); - content::WaitForLoadStop(tab_strip->GetActiveWebContents()); - - EXPECT_EQ(app_browser->GetWindowTitleFromWebContents( - false, tab_strip->GetWebContentsAt(0)), - u"Tab Strip Customizations"); - EXPECT_EQ(app_browser->GetWindowTitleFromWebContents( - false, tab_strip->GetWebContentsAt(1)), - u"Favicon only"); -} - } // namespace web_app
diff --git a/chrome/browser/ui/webui/app_home/DEPS b/chrome/browser/ui/webui/app_home/DEPS new file mode 100644 index 0000000..6d42e07 --- /dev/null +++ b/chrome/browser/ui/webui/app_home/DEPS
@@ -0,0 +1,5 @@ +specific_include_rules = { + ".*test\.cc": [ + "+chrome/browser/ui/views/create_application_shortcut_view_test_support.h", + ] +}
diff --git a/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc b/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc index 31b1aec..ab360f3 100644 --- a/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc +++ b/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/ui/webui/app_home/app_home_page_handler.h" +#include <utility> #include <vector> #include "base/strings/utf_string_conversions.h" @@ -11,6 +12,7 @@ #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/test_extension_system.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/views/create_application_shortcut_view_test_support.h" #include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h" #include "chrome/browser/ui/webui/app_home/app_home.mojom.h" #include "chrome/browser/ui/webui/app_home/mock_app_home_page.h" @@ -431,13 +433,10 @@ page_handler->CreateAppShortcut(installed_app_id, loop.QuitClosure()); loop.Run(); #else - views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{}, - "CreateChromeApplicationShortcutView"); + CreateChromeApplicationShortcutViewWaiter waiter; page_handler->CreateAppShortcut(installed_app_id, base::DoNothing()); FlushShortcutTasks(); - views::Widget* widget = waiter.WaitIfNeededAndGet(); - ASSERT_TRUE(widget != nullptr); - views::test::AcceptDialog(widget); + std::move(waiter).WaitForAndAccept(); FlushShortcutTasks(); #endif EXPECT_CALL(page_, RemoveApp(MatchAppId(installed_app_id))) @@ -472,13 +471,10 @@ page_handler->CreateAppShortcut(extension->id(), loop.QuitClosure()); loop.Run(); #else - views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{}, - "CreateChromeApplicationShortcutView"); + CreateChromeApplicationShortcutViewWaiter waiter; page_handler->CreateAppShortcut(extension->id(), base::DoNothing()); FlushShortcutTasks(); - views::Widget* widget = waiter.WaitIfNeededAndGet(); - ASSERT_TRUE(widget != nullptr); - views::test::AcceptDialog(widget); + std::move(waiter).WaitForAndAccept(); #endif EXPECT_CALL(page_, RemoveApp(MatchAppId(extension->id()))) .Times(testing::AtLeast(1));
diff --git a/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler.cc b/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler.cc index 7f9aa2f..d8adfc1 100644 --- a/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler.cc +++ b/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler.cc
@@ -23,6 +23,8 @@ // static const char StatusAreaInternalsHandler::kToggleIme[] = "toggleIme"; const char StatusAreaInternalsHandler::kTogglePalette[] = "togglePalette"; +const char StatusAreaInternalsHandler::kTriggerPrivacyIndicators[] = + "triggerPrivacyIndicators"; StatusAreaInternalsHandler::StatusAreaInternalsHandler() = default; @@ -37,6 +39,10 @@ kTogglePalette, base::BindRepeating(&StatusAreaInternalsHandler::TogglePaletteTray, weak_pointer_factory_.GetWeakPtr())); + web_ui()->RegisterMessageCallback( + kTriggerPrivacyIndicators, + base::BindRepeating(&StatusAreaInternalsHandler::TriggerPrivacyIndicators, + weak_pointer_factory_.GetWeakPtr())); } void StatusAreaInternalsHandler::SetWebUiForTesting(content::WebUI* web_ui) { @@ -76,4 +82,20 @@ } } +void StatusAreaInternalsHandler::TriggerPrivacyIndicators( + const base::Value::List& args) { + AllowJavascript(); + + // Parse JS args. + auto app_id = args[0].GetString(); + auto app_name = args[1].GetString(); + auto is_camera_used = args[2].GetBool(); + auto is_microphone_used = args[3].GetBool(); + + PrivacyIndicatorsController::Get()->UpdatePrivacyIndicators( + app_id, base::UTF8ToUTF16(app_name), is_camera_used, is_microphone_used, + base::MakeRefCounted<PrivacyIndicatorsNotificationDelegate>(), + PrivacyIndicatorsSource::kApps); +} + } // namespace ash
diff --git a/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler.h b/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler.h index e07b4715..7d49d87 100644 --- a/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler.h +++ b/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler.h
@@ -27,6 +27,7 @@ // Handler names static const char kToggleIme[]; static const char kTogglePalette[]; + static const char kTriggerPrivacyIndicators[]; // content::WebUIMessageHandler: void RegisterMessages() override; @@ -37,6 +38,7 @@ // Callbacks for events coming from the web UI. void ToggleImeTray(const base::Value::List& args); void TogglePaletteTray(const base::Value::List& args); + void TriggerPrivacyIndicators(const base::Value::List& args); base::WeakPtrFactory<StatusAreaInternalsHandler> weak_pointer_factory_{this}; };
diff --git a/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler_unittest.cc b/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler_unittest.cc index ee60dcc..ce76f2756 100644 --- a/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler_unittest.cc +++ b/chrome/browser/ui/webui/ash/status_area_internals/status_area_internals_handler_unittest.cc
@@ -11,7 +11,9 @@ #include "ash/session/session_controller_impl.h" #include "ash/shell.h" #include "ash/system/ime_menu/ime_menu_tray.h" +#include "ash/system/notification_center/notification_center_tray.h" #include "ash/system/palette/palette_tray.h" +#include "ash/system/privacy/privacy_indicators_tray_item_view.h" #include "ash/system/status_area_widget.h" #include "ash/test/ash_test_base.h" #include "components/prefs/pref_service.h" @@ -97,4 +99,31 @@ EXPECT_FALSE(palette_tray->GetVisible()); } +// Sending `kTriggerPrivacyIndicators` message from the web UI should update the +// visibility of the privacy indicators accordingly. +TEST_F(StatusAreaInternalsHandlerTest, TriggerPrivacyIndicators) { + auto* privacy_indicators_view = GetStatusAreaWidget() + ->notification_center_tray() + ->privacy_indicators_view(); + ASSERT_FALSE(privacy_indicators_view->GetVisible()); + + base::Value::List args; + args.Append("app_id"); + args.Append("app_name"); + args.Append(true); + args.Append(true); + SendMessage(StatusAreaInternalsHandler::kTriggerPrivacyIndicators, args); + + EXPECT_TRUE(privacy_indicators_view->GetVisible()); + + args.clear(); + args.Append("app_id"); + args.Append("app_name"); + args.Append(false); + args.Append(false); + SendMessage(StatusAreaInternalsHandler::kTriggerPrivacyIndicators, args); + + EXPECT_FALSE(privacy_indicators_view->GetVisible()); +} + } // namespace ash
diff --git a/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs_desktop.cc b/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs_desktop.cc index b2968bb..1be54e9 100644 --- a/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs_desktop.cc +++ b/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs_desktop.cc
@@ -10,6 +10,7 @@ #include "chrome/browser/companion/core/features.h" #include "chrome/browser/ui/side_panel/companion/companion_utils.h" #include "chrome/browser/ui/webui/feed/feed_ui_config.h" +#include "chrome/browser/ui/webui/hats/hats_ui.h" #include "chrome/browser/ui/webui/side_panel/companion/companion_side_panel_untrusted_ui.h" #include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.h" #include "components/lens/buildflags.h" @@ -32,6 +33,7 @@ } map.AddUntrustedWebUIConfig( std::make_unique<ReadAnythingUIUntrustedConfig>()); + map.AddUntrustedWebUIConfig(std::make_unique<HatsUIConfig>()); #if BUILDFLAG(ENABLE_LENS_DESKTOP_GOOGLE_BRANDED_FEATURES) map.AddUntrustedWebUIConfig(std::make_unique<lens::LensUntrustedUIConfig>());
diff --git a/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc b/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc index a3646ac7..a9c4f6f 100644 --- a/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc +++ b/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc
@@ -114,7 +114,13 @@ // Makes sure navigating to the new tab page results in a http status code // of 200. -IN_PROC_BROWSER_TEST_F(ChromeURLDataManagerTest, 200) { +// TODO(crbug.com/1473471) Test Failing on Mac11 tests +#if BUILDFLAG(IS_MAC) +#define MAYBE_200 DISABLED_200 +#else +#define MAYBE_200 200 +#endif +IN_PROC_BROWSER_TEST_F(ChromeURLDataManagerTest, MAYBE_200) { NavigationObserver observer( browser()->tab_strip_model()->GetActiveWebContents()); ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
diff --git a/chrome/browser/ui/webui/engagement/site_engagement_ui.cc b/chrome/browser/ui/webui/engagement/site_engagement_ui.cc index c120525b3..b792b30 100644 --- a/chrome/browser/ui/webui/engagement/site_engagement_ui.cc +++ b/chrome/browser/ui/webui/engagement/site_engagement_ui.cc
@@ -13,6 +13,7 @@ #include "base/functional/callback.h" #include "base/memory/raw_ptr.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/webui/webui_util.h" #include "chrome/common/url_constants.h" #include "chrome/grit/engagement_resources.h" #include "chrome/grit/engagement_resources_map.h" @@ -98,12 +99,9 @@ // Set up the chrome://site-engagement/ source. content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd( Profile::FromWebUI(web_ui), chrome::kChromeUISiteEngagementHost); - source->OverrideContentSecurityPolicy( - network::mojom::CSPDirectiveName::ScriptSrc, - "script-src chrome://resources chrome://webui-test 'self';"); - source->AddResourcePaths( - base::make_span(kEngagementResources, kEngagementResourcesSize)); - source->SetDefaultResource(IDR_ENGAGEMENT_SITE_ENGAGEMENT_HTML); + webui::SetupWebUIDataSource( + source, base::make_span(kEngagementResources, kEngagementResourcesSize), + IDR_ENGAGEMENT_SITE_ENGAGEMENT_HTML); } WEB_UI_CONTROLLER_TYPE_IMPL(SiteEngagementUI)
diff --git a/chrome/browser/ui/webui/hats/DIR_METADATA b/chrome/browser/ui/webui/hats/DIR_METADATA new file mode 100644 index 0000000..4b06f183 --- /dev/null +++ b/chrome/browser/ui/webui/hats/DIR_METADATA
@@ -0,0 +1,3 @@ +monorail { + component: "UI>Browser>HaTS" +}
diff --git a/chrome/browser/ui/webui/hats/OWNERS b/chrome/browser/ui/webui/hats/OWNERS new file mode 100644 index 0000000..bbdb9fd --- /dev/null +++ b/chrome/browser/ui/webui/hats/OWNERS
@@ -0,0 +1 @@ +file://chrome/browser/ui/hats/OWNERS
diff --git a/chrome/browser/ui/webui/hats/hats_ui.cc b/chrome/browser/ui/webui/hats/hats_ui.cc new file mode 100644 index 0000000..7365086 --- /dev/null +++ b/chrome/browser/ui/webui/hats/hats_ui.cc
@@ -0,0 +1,41 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/webui/hats/hats_ui.h" + +#include "chrome/browser/ui/ui_features.h" +#include "chrome/browser/ui/webui/webui_util.h" +#include "chrome/common/webui_url_constants.h" +#include "chrome/grit/hats_resources.h" +#include "chrome/grit/hats_resources_map.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui.h" +#include "content/public/browser/web_ui_data_source.h" + +HatsUIConfig::HatsUIConfig() + : WebUIConfig(content::kChromeUIUntrustedScheme, + chrome::kChromeUIUntrustedHatsHost) {} + +bool HatsUIConfig::IsWebUIEnabled(content::BrowserContext* browser_context) { + return base::FeatureList::IsEnabled(features::kHaTSWebUI); +} + +std::unique_ptr<content::WebUIController> HatsUIConfig::CreateWebUIController( + content::WebUI* web_ui, + const GURL& url) { + return std::make_unique<HatsUI>(web_ui); +} + +HatsUI::HatsUI(content::WebUI* web_ui) : ui::UntrustedWebUIController(web_ui) { + content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd( + web_ui->GetWebContents()->GetBrowserContext(), + chrome::kChromeUIUntrustedHatsURL); + + // Add required resources. + webui::SetupWebUIDataSource( + source, base::make_span(kHatsResources, kHatsResourcesSize), + IDR_HATS_HATS_HTML); +} + +WEB_UI_CONTROLLER_TYPE_IMPL(HatsUI)
diff --git a/chrome/browser/ui/webui/hats/hats_ui.h b/chrome/browser/ui/webui/hats/hats_ui.h new file mode 100644 index 0000000..95bca34 --- /dev/null +++ b/chrome/browser/ui/webui/hats/hats_ui.h
@@ -0,0 +1,37 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_WEBUI_HATS_HATS_UI_H_ +#define CHROME_BROWSER_UI_WEBUI_HATS_HATS_UI_H_ + +#include "content/public/browser/webui_config.h" +#include "ui/webui/untrusted_web_ui_controller.h" + +// The configuration for the chrome-untrusted://hats page. +class HatsUIConfig : public content::WebUIConfig { + public: + HatsUIConfig(); + ~HatsUIConfig() override = default; + + bool IsWebUIEnabled(content::BrowserContext* browser_context) override; + + std::unique_ptr<content::WebUIController> CreateWebUIController( + content::WebUI* web_ui, + const GURL& url) override; +}; + +class HatsUI : public ui::UntrustedWebUIController { + public: + explicit HatsUI(content::WebUI* web_ui); + + HatsUI(const HatsUI&) = delete; + HatsUI& operator=(const HatsUI&) = delete; + + ~HatsUI() override = default; + + private: + WEB_UI_CONTROLLER_TYPE_DECL(); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_HATS_HATS_UI_H_
diff --git a/chrome/browser/ui/webui/settings/ash/device_section.cc b/chrome/browser/ui/webui/settings/ash/device_section.cc index 952463b..898eaff 100644 --- a/chrome/browser/ui/webui/settings/ash/device_section.cc +++ b/chrome/browser/ui/webui/settings/ash/device_section.cc
@@ -1926,18 +1926,26 @@ {"displayOverscanInstructions", IDS_SETTINGS_DISPLAY_OVERSCAN_INSTRUCTIONS}, {"displayOverscanPageText", IDS_SETTINGS_DISPLAY_OVERSCAN_TEXT}, - {"displayOverscanPageTitle", IDS_SETTINGS_DISPLAY_OVERSCAN_TITLE}, + {"displayOverscanPageTitle", + kIsRevampEnabled ? IDS_OS_SETTINGS_REVAMP_DISPLAY_BOUNDARIES_TITLE + : IDS_SETTINGS_DISPLAY_OVERSCAN_TITLE}, {"displayOverscanPosition", IDS_SETTINGS_DISPLAY_OVERSCAN_POSITION}, {"displayOverscanResize", IDS_SETTINGS_DISPLAY_OVERSCAN_RESIZE}, {"displayOverscanReset", IDS_SETTINGS_DISPLAY_OVERSCAN_RESET}, - {"displayOverscanSubtitle", IDS_SETTINGS_DISPLAY_OVERSCAN_SUBTITLE}, + {"displayOverscanSubtitle", + kIsRevampEnabled ? IDS_OS_SETTINGS_REVAMP_DISPLAY_BOUNDARIES_DESCRIPTION + : IDS_SETTINGS_DISPLAY_OVERSCAN_SUBTITLE}, {"displayRefreshRateInterlacedMenuItem", IDS_SETTINGS_DISPLAY_REFRESH_RATE_INTERLACED_MENU_ITEM}, {"displayRefreshRateMenuItem", IDS_SETTINGS_DISPLAY_REFRESH_RATE_MENU_ITEM}, {"displayRefreshRateSublabel", - IDS_SETTINGS_DISPLAY_REFRESH_RATE_SUBLABEL}, - {"displayRefreshRateTitle", IDS_SETTINGS_DISPLAY_REFRESH_RATE_TITLE}, + kIsRevampEnabled + ? IDS_OS_SETTINGS_REVAMP_DISPLAY_REFRESH_RATE_DESCRIPTION + : IDS_SETTINGS_DISPLAY_REFRESH_RATE_SUBLABEL}, + {"displayRefreshRateTitle", + kIsRevampEnabled ? IDS_OS_SETTINGS_REVAMP_DISPLAY_REFRESH_RATE_TITLE + : IDS_SETTINGS_DISPLAY_REFRESH_RATE_TITLE}, {"displayResolutionInterlacedMenuItem", IDS_SETTINGS_DISPLAY_RESOLUTION_INTERLACED_MENU_ITEM}, {"displayResolutionMenuItem", IDS_SETTINGS_DISPLAY_RESOLUTION_MENU_ITEM}, @@ -1969,8 +1977,12 @@ IDS_SETTINGS_DISPLAY_ZOOM_LOGICAL_RESOLUTION_TEXT}, {"displayZoomNativeLogicalResolutionNativeText", IDS_SETTINGS_DISPLAY_ZOOM_LOGICAL_RESOLUTION_NATIVE_TEXT}, - {"displayZoomSublabel", IDS_SETTINGS_DISPLAY_ZOOM_SUBLABEL}, - {"displayZoomTitle", IDS_SETTINGS_DISPLAY_ZOOM_TITLE}, + {"displayZoomLabel", kIsRevampEnabled + ? IDS_OS_SETTINGS_REVAMP_DISPLAY_ZOOM_LABEL + : IDS_SETTINGS_DISPLAY_ZOOM_TITLE}, + {"displayZoomDescription", + kIsRevampEnabled ? IDS_OS_SETTINGS_REVAMP_DISPLAY_ZOOM_DESCRIPTION + : IDS_SETTINGS_DISPLAY_ZOOM_SUBLABEL}, {"displayZoomValue", IDS_SETTINGS_DISPLAY_ZOOM_VALUE}, }; html_source->AddLocalizedStrings(kDisplayStrings);
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 33caaad..1143d2f 100644 --- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc +++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1909,7 +1909,6 @@ IDS_SETTINGS_RECENT_PERMISSIONS_BLOCKED_TWO_ITEMS}, {"recentPermissionBlockedMoreThanTwoItems", IDS_SETTINGS_RECENT_PERMISSIONS_BLOCKED_MORE_THAN_TWO_ITEMS}, - {"networkPredictionEnabled", IDS_SETTINGS_NETWORK_PREDICTION_ENABLED_LABEL}, {"networkPredictionEnabledDesc", IDS_SETTINGS_NETWORK_PREDICTION_ENABLED_DESC}, {"networkPredictionEnabledDescCookiesPage",
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc index 9744a37d..d011781 100644 --- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc +++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
@@ -124,6 +124,13 @@ #else false); #endif + + source->AddBoolean( + "extensionsCardEnabled", + base::FeatureList::IsEnabled( + ntp_features::kCustomizeChromeSidePanelExtensionsCard) && + features::IsChromeWebuiRefresh2023()); + webui::SetupChromeRefresh2023(source); webui::SetupWebUIDataSource(
diff --git a/chrome/browser/ui/webui/version/version_handler_chromeos.cc b/chrome/browser/ui/webui/version/version_handler_chromeos.cc index 29a2ee3..a665968 100644 --- a/chrome/browser/ui/webui/version/version_handler_chromeos.cc +++ b/chrome/browser/ui/webui/version/version_handler_chromeos.cc
@@ -5,28 +5,38 @@ #include "chrome/browser/ui/webui/version/version_handler_chromeos.h" #include "base/functional/bind.h" +#include "base/strings/utf_string_conversions.h" #include "base/task/thread_pool.h" -#include "build/chromeos_buildflags.h" #include "chrome/common/channel_info.h" #include "chrome/common/webui_url_constants.h" +#include "chromeos/strings/grit/chromeos_strings.h" +#include "chromeos/version/version_loader.h" #include "components/version_info/channel.h" #include "content/public/browser/web_ui.h" +#include "ui/base/l10n/l10n_util.h" #include "url/gurl.h" #if BUILDFLAG(IS_CHROMEOS_ASH) -#include "base/strings/utf_string_conversions.h" #include "chrome/browser/ash/crosapi/browser_manager.h" #include "chrome/browser/ash/crosapi/browser_util.h" -#include "chromeos/strings/grit/chromeos_strings.h" -#include "ui/base/l10n/l10n_util.h" -#elif BUILDFLAG(IS_CHROMEOS_LACROS) +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + +#if BUILDFLAG(IS_CHROMEOS_LACROS) #include "chrome/browser/lacros/lacros_url_handling.h" -#endif +#include "chromeos/startup/browser_params_proxy.h" +#endif // BUILDFLAG(IS_CHROMEOS_LACROS) namespace { const char kCrosUrlVersionRedirect[] = "crosUrlVersionRedirect"; +#if BUILDFLAG(IS_CHROMEOS_LACROS) +std::string GetOsVersion() { + return chromeos::BrowserParamsProxy::Get()->AshChromeVersion().value_or( + "0.0.0.0"); +} +#endif // BUILDFLAG(IS_CHROMEOS_LACROS) + } // namespace VersionHandlerChromeOS::VersionHandlerChromeOS() {} @@ -42,18 +52,24 @@ const base::Value::List& args) { VersionHandler::HandleRequestVersionInfo(args); -#if BUILDFLAG(IS_CHROMEOS_ASH) // Start the asynchronous load of the versions. +#if BUILDFLAG(IS_CHROMEOS_LACROS) + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, + base::BindOnce(&GetOsVersion), + base::BindOnce(&VersionHandlerChromeOS::OnOsVersion, + weak_factory_.GetWeakPtr())); +#endif base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, base::BindOnce(&chromeos::version_loader::GetVersion, chromeos::version_loader::VERSION_FULL), - base::BindOnce(&VersionHandlerChromeOS::OnVersion, + base::BindOnce(&VersionHandlerChromeOS::OnPlatformVersion, weak_factory_.GetWeakPtr())); base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, base::BindOnce(&chromeos::version_loader::GetFirmware), - base::BindOnce(&VersionHandlerChromeOS::OnOSFirmware, + base::BindOnce(&VersionHandlerChromeOS::OnFirmwareVersion, weak_factory_.GetWeakPtr())); base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, @@ -61,6 +77,7 @@ base::BindOnce(&VersionHandlerChromeOS::OnArcAndArcAndroidSdkVersions, weak_factory_.GetWeakPtr())); +#if BUILDFLAG(IS_CHROMEOS_ASH) const bool showSystemFlagsLink = crosapi::browser_util::IsLacrosEnabled(); #else const bool showSystemFlagsLink = true; @@ -91,15 +108,20 @@ #endif } -#if BUILDFLAG(IS_CHROMEOS_ASH) -void VersionHandlerChromeOS::OnVersion( +#if BUILDFLAG(IS_CHROMEOS_LACROS) +void VersionHandlerChromeOS::OnOsVersion(const std::string& version) { + FireWebUIListener("return-os-version", base::Value(version)); +} +#endif + +void VersionHandlerChromeOS::OnPlatformVersion( const absl::optional<std::string>& version) { - FireWebUIListener("return-os-version", + FireWebUIListener("return-platform-version", base::Value(version.value_or("0.0.0.0"))); } -void VersionHandlerChromeOS::OnOSFirmware(const std::string& version) { - FireWebUIListener("return-os-firmware-version", base::Value(version)); +void VersionHandlerChromeOS::OnFirmwareVersion(const std::string& version) { + FireWebUIListener("return-firmware-version", base::Value(version)); } void VersionHandlerChromeOS::OnArcAndArcAndroidSdkVersions( @@ -124,5 +146,3 @@ arc_android_sdk_version->c_str()); return labeled_version; } - -#endif // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/webui/version/version_handler_chromeos.h b/chrome/browser/ui/webui/version/version_handler_chromeos.h index 5f255ac8..8453906 100644 --- a/chrome/browser/ui/webui/version/version_handler_chromeos.h +++ b/chrome/browser/ui/webui/version/version_handler_chromeos.h
@@ -8,8 +8,8 @@ #include <string> #include "base/memory/weak_ptr.h" +#include "build/chromeos_buildflags.h" #include "chrome/browser/ui/webui/version/version_handler.h" -#include "chromeos/version/version_loader.h" // VersionHandlerChromeOS is responsible for loading the Chrome OS // version. @@ -28,8 +28,11 @@ void RegisterMessages() override; // Callbacks from chromeos::VersionLoader. - void OnVersion(const absl::optional<std::string>& version); - void OnOSFirmware(const std::string& version); +#if BUILDFLAG(IS_CHROMEOS_LACROS) + void OnOsVersion(const std::string& version); +#endif + void OnPlatformVersion(const absl::optional<std::string>& version); + void OnFirmwareVersion(const std::string& version); void OnArcAndArcAndroidSdkVersions(const std::string& version); // Callback for the "crosUrlVersionRedirect" message.
diff --git a/chrome/browser/ui/webui/version/version_ui.cc b/chrome/browser/ui/webui/version/version_ui.cc index 82a6e68..f4db1d0 100644 --- a/chrome/browser/ui/webui/version/version_ui.cc +++ b/chrome/browser/ui/webui/version/version_ui.cc
@@ -64,10 +64,6 @@ #include "chrome/browser/ui/webui/version/version_util_win.h" #endif -#if BUILDFLAG(IS_CHROMEOS_LACROS) -#include "chromeos/startup/browser_params_proxy.h" -#endif // BUILDFLAG(IS_CHROMEOS_LACROS) - using content::WebUIDataSource; namespace { @@ -90,19 +86,18 @@ {version_ui::kVariationsName, IDS_VERSION_UI_VARIATIONS}, {version_ui::kVariationsCmdName, IDS_VERSION_UI_VARIATIONS_CMD}, {version_ui::kVariationsSeedName, IDS_VERSION_UI_VARIATIONS_SEED_NAME}, -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) {version_ui::kARC, IDS_ARC_LABEL}, {version_ui::kPlatform, IDS_PLATFORM_LABEL}, {version_ui::kCustomizationId, IDS_VERSION_UI_CUSTOMIZATION_ID}, {version_ui::kFirmwareVersion, IDS_VERSION_UI_FIRMWARE_VERSION}, -#else - {version_ui::kOSName, IDS_VERSION_UI_OS}, -#endif // BUILDFLAG(IS_CHROMEOS_ASH) -#if BUILDFLAG(IS_CHROMEOS) {version_ui::kOsVersionHeaderText1, IDS_VERSION_UI_OS_TEXT1_LABEL}, {version_ui::kOsVersionHeaderText2, IDS_VERSION_UI_OS_TEXT2_LABEL}, {version_ui::kOsVersionHeaderLink, IDS_VERSION_UI_OS_LINK}, #endif // BUILDFLAG(IS_CHROMEOS) +#if !BUILDFLAG(IS_CHROMEOS_ASH) + {version_ui::kOSName, IDS_VERSION_UI_OS}, +#endif // !BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_ANDROID) {version_ui::kGmsName, IDS_VERSION_UI_GMS}, #endif // BUILDFLAG(IS_ANDROID) @@ -225,11 +220,6 @@ html_source->AddString(version_ui::kVersionModifier, GetProductModifier()); -#if BUILDFLAG(IS_CHROMEOS_LACROS) - auto* init_params = chromeos::BrowserParamsProxy::Get(); - html_source->AddString(version_ui::kAshChromeVersion, - init_params->AshChromeVersion().value_or("0.0.0.0")); -#endif // BUILDFLAG(IS_CHROMEOS_LACROS) html_source->AddString(version_ui::kJSEngine, "V8"); html_source->AddString(version_ui::kJSVersion, V8_VERSION_STRING); html_source->AddString(
diff --git a/chrome/browser/ui/webui/web_app_internals/BUILD.gn b/chrome/browser/ui/webui/web_app_internals/BUILD.gn index 5efa69d..2165998 100644 --- a/chrome/browser/ui/webui/web_app_internals/BUILD.gn +++ b/chrome/browser/ui/webui/web_app_internals/BUILD.gn
@@ -6,6 +6,9 @@ mojom("mojo_bindings") { sources = [ "web_app_internals.mojom" ] + + public_deps = [ "//url/mojom:url_mojom_gurl" ] + webui_module_path = "/" use_typescript_sources = true }
diff --git a/chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom b/chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom index 56c1d94..e3087f7 100644 --- a/chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom +++ b/chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom
@@ -4,6 +4,13 @@ module mojom; +import "url/mojom/url.mojom"; + +struct InstallIsolatedWebAppFromDevProxyResult { + bool success; // true if app was installed successfully. + string error; // should only be set if the app failed to install. +}; + // Handles requests from chrome://web-app-internals. // This is expected to be hosted in the browser process. interface WebAppInternalsHandler { @@ -12,4 +19,7 @@ // Returns whether the clearing is successful. [EnableIf=is_chromeos_lacros] ClearExperimentalWebAppIsolationData() => (bool success); + // Returns whether the installation succeeded. + InstallIsolatedWebAppFromDevProxy(url.mojom.Url url) => + (InstallIsolatedWebAppFromDevProxyResult result); };
diff --git a/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.cc b/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.cc index 718a45f..bc34b012 100644 --- a/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.cc +++ b/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.cc
@@ -414,6 +414,46 @@ std::move(value_to_string).Then(std::move(callback)))); } +void WebAppInternalsHandler::InstallIsolatedWebAppFromDevProxy( + const GURL& url, + InstallIsolatedWebAppFromDevProxyCallback callback) { + if (!web_app::AreWebAppsEnabled(profile_)) { + ::mojom::InstallIsolatedWebAppFromDevProxyResult mojo_result; + mojo_result.success = false; + mojo_result.error = std::string("web apps not enabled"); + std::move(callback).Run(mojo_result.Clone()); + return; + } + + auto* provider = web_app::WebAppProvider::GetForWebApps(profile_); + if (!provider) { + ::mojom::InstallIsolatedWebAppFromDevProxyResult mojo_result; + mojo_result.success = false; + mojo_result.error = std::string("could not get web app provider"); + std::move(callback).Run(mojo_result.Clone()); + return; + } + + auto& manager = provider->iwa_command_line_install_manager(); + manager.InstallIsolatedWebAppFromDevModeProxy( + url, base::BindOnce( + &WebAppInternalsHandler::OnInstallIsolatedWebAppFromDevModeProxy, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); +} + +void WebAppInternalsHandler::OnInstallIsolatedWebAppFromDevModeProxy( + WebAppInternalsHandler::InstallIsolatedWebAppFromDevProxyCallback callback, + web_app::MaybeInstallIsolatedWebAppCommandSuccess result) { + ::mojom::InstallIsolatedWebAppFromDevProxyResult mojo_result; + if (result.has_value()) { + mojo_result.success = true; + } else { + mojo_result.success = false; + mojo_result.error = result.error(); + } + std::move(callback).Run(mojo_result.Clone()); +} + #if BUILDFLAG(IS_CHROMEOS_LACROS) void WebAppInternalsHandler::ClearExperimentalWebAppIsolationData( ClearExperimentalWebAppIsolationDataCallback callback) {
diff --git a/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h b/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h index ede274f1..47343ac 100644 --- a/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h +++ b/chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h
@@ -8,6 +8,7 @@ #include "base/functional/callback_forward.h" #include "base/values.h" #include "chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom.h" +#include "chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/receiver.h" @@ -33,14 +34,22 @@ // mojom::WebAppInternalsHandler: void GetDebugInfoAsJsonString( GetDebugInfoAsJsonStringCallback callback) override; + void InstallIsolatedWebAppFromDevProxy( + const GURL& url, + InstallIsolatedWebAppFromDevProxyCallback callback) override; #if BUILDFLAG(IS_CHROMEOS_LACROS) void ClearExperimentalWebAppIsolationData( ClearExperimentalWebAppIsolationDataCallback callback) override; #endif // BUILDFLAG(IS_CHROMEOS_LACROS) private: + void OnInstallIsolatedWebAppFromDevModeProxy( + InstallIsolatedWebAppFromDevProxyCallback callback, + web_app::MaybeInstallIsolatedWebAppCommandSuccess result); + const raw_ptr<Profile> profile_; mojo::Receiver<mojom::WebAppInternalsHandler> receiver_; + base::WeakPtrFactory<WebAppInternalsHandler> weak_ptr_factory_{this}; }; #endif // CHROME_BROWSER_UI_WEBUI_WEB_APP_INTERNALS_WEB_APP_INTERNALS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/web_app_internals/web_app_internals_ui.cc b/chrome/browser/ui/webui/web_app_internals/web_app_internals_ui.cc index 84aba54..0948ead 100644 --- a/chrome/browser/ui/webui/web_app_internals/web_app_internals_ui.cc +++ b/chrome/browser/ui/webui/web_app_internals/web_app_internals_ui.cc
@@ -8,6 +8,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h" #include "chrome/browser/ui/webui/webui_util.h" +#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_dev_mode.h" #include "chrome/common/url_constants.h" #include "chrome/grit/web_app_internals_resources.h" #include "chrome/grit/web_app_internals_resources_map.h" @@ -29,9 +30,12 @@ internals, base::make_span(kWebAppInternalsResources, kWebAppInternalsResourcesSize), IDR_WEB_APP_INTERNALS_WEB_APP_INTERNALS_HTML); + internals->UseStringsJs(); + internals->AddBoolean( + "experimentalIsIwaDevModeEnabled", + web_app::IsIwaDevModeEnabled(Profile::FromWebUI(web_ui))); #if BUILDFLAG(IS_CHROMEOS_LACROS) - internals->UseStringsJs(); internals->AddBoolean( "experimentalIsolationEnabled", web_app::ResolveExperimentalWebAppIsolationFeature() !=
diff --git a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.cc b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.cc index 3946445..2c2f260 100644 --- a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.cc +++ b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.cc
@@ -98,33 +98,32 @@ std::move(callback)); } +MaybeIwaLocation GetProxyUrl(const GURL& gurl) { + url::Origin url_origin = url::Origin::Create(gurl); + + // The .is_valid() check here will also capture an empty URL. + if (!gurl.is_valid() || url_origin.opaque()) { + return base::unexpected( + base::StrCat({"Invalid URL provided: ", gurl.possibly_invalid_spec()})); + } + + if (url_origin.GetURL() != gurl) { + return base::unexpected(base::StrCat( + {"Non-origin URL provided: '", gurl.possibly_invalid_spec(), "'", + ". Possible origin URL: '", url_origin.Serialize(), "'."})); + } + + return DevModeProxy{.proxy_url = url_origin}; +} + MaybeIwaLocation GetProxyUrlFromCommandLine( const base::CommandLine& command_line) { std::string switch_value = command_line.GetSwitchValueASCII(switches::kInstallIsolatedWebAppFromUrl); - if (switch_value.empty()) { return absl::nullopt; } - - GURL url{switch_value}; - url::Origin url_origin = url::Origin::Create(url); - - if (!url.is_valid() || url_origin.opaque()) { - return base::unexpected(base::StrCat( - {"Invalid URL provided to --", switches::kInstallIsolatedWebAppFromUrl, - " flag: '", url.possibly_invalid_spec(), "'"})); - } - - if (url_origin.GetURL() != url) { - return base::unexpected(base::StrCat( - {"Non-origin URL provided to --", - switches::kInstallIsolatedWebAppFromUrl, " flag: '", - url.possibly_invalid_spec(), "'", ". Possible origin URL: '", - url_origin.Serialize(), "'."})); - } - - return DevModeProxy{.proxy_url = url_origin}; + return GetProxyUrl(GURL(switch_value)); } } // namespace @@ -218,8 +217,8 @@ std::unique_ptr<ScopedKeepAlive> keep_alive, std::unique_ptr<ScopedProfileKeepAlive> optional_profile_keep_alive, MaybeIwaLocation location, - base::OnceCallback<void(base::expected<InstallIsolatedWebAppCommandSuccess, - std::string>)> callback) { + base::OnceCallback<void(MaybeInstallIsolatedWebAppCommandSuccess)> + callback) { ASSIGN_OR_RETURN( absl::optional<IsolatedWebAppLocation> optional_location, location, [&](std::string error) { @@ -245,6 +244,45 @@ } void IsolatedWebAppCommandLineInstallManager:: + InstallIsolatedWebAppFromDevModeProxy( + const GURL& gurl, + base::OnceCallback<void(MaybeInstallIsolatedWebAppCommandSuccess)> + callback) { + CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + CHECK(!callback.is_null()); + + MaybeInstallIsolatedWebAppCommandSuccess result; + if (KeepAliveRegistry::GetInstance()->IsShuttingDown()) { + // If the browser is shutting down, then there is no point in attempting to + // install an IWA. + std::move(callback).Run(base::unexpected("Browser is shutting down")); + return; + } + auto keep_alive = std::make_unique<ScopedKeepAlive>( + KeepAliveOrigin::ISOLATED_WEB_APP_INSTALL, + KeepAliveRestartOption::DISABLED); + if (profile_->IsOffTheRecord()) { + std::move(callback).Run( + base::unexpected(std::string("incognito profiles are not supported"))); + return; + } + std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive = + std::make_unique<ScopedProfileKeepAlive>( + &profile_.get(), ProfileKeepAliveOrigin::kIsolatedWebAppInstall); + + // Ensure the URL we're given is okay. + MaybeIwaLocation location = GetProxyUrl(gurl); + if (!location.has_value()) { + std::move(callback).Run(base::unexpected(location.error())); + return; + } + + InstallIsolatedWebAppFromLocation(std::move(keep_alive), + std::move(profile_keep_alive), + std::move(location), std::move(callback)); +} + +void IsolatedWebAppCommandLineInstallManager:: OnGetIsolatedWebAppLocationFromCommandLine( std::unique_ptr<ScopedKeepAlive> keep_alive, std::unique_ptr<ScopedProfileKeepAlive> optional_profile_keep_alive, @@ -261,8 +299,7 @@ std::unique_ptr<ScopedKeepAlive> keep_alive, std::unique_ptr<ScopedProfileKeepAlive> optional_profile_keep_alive, const IsolatedWebAppLocation& location, - base::OnceCallback<void(base::expected<InstallIsolatedWebAppCommandSuccess, - std::string>)> callback, + base::OnceCallback<void(MaybeInstallIsolatedWebAppCommandSuccess)> callback, base::expected<IsolatedWebAppUrlInfo, std::string> url_info) { RETURN_IF_ERROR(url_info, [&](std::string error) { std::move(callback).Run( @@ -279,8 +316,7 @@ } void IsolatedWebAppCommandLineInstallManager::OnInstallIsolatedWebApp( - base::OnceCallback<void(base::expected<InstallIsolatedWebAppCommandSuccess, - std::string>)> callback, + base::OnceCallback<void(MaybeInstallIsolatedWebAppCommandSuccess)> callback, base::expected<InstallIsolatedWebAppCommandSuccess, InstallIsolatedWebAppCommandError> result) { std::move(callback).Run( @@ -288,7 +324,7 @@ } void IsolatedWebAppCommandLineInstallManager::ReportInstallationResult( - base::expected<InstallIsolatedWebAppCommandSuccess, std::string> result) { + MaybeInstallIsolatedWebAppCommandSuccess result) { if (result.has_value()) { LOG(WARNING) << "Isolated Web App command line installation successful."; } else {
diff --git a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.h b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.h index a41d517..8228b30b 100644 --- a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.h +++ b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.h
@@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO: Rename this file since it's used for more than command-line +// installations now. + #ifndef CHROME_BROWSER_WEB_APPLICATIONS_ISOLATED_WEB_APPS_INSTALL_ISOLATED_WEB_APP_FROM_COMMAND_LINE_H_ #define CHROME_BROWSER_WEB_APPLICATIONS_ISOLATED_WEB_APPS_INSTALL_ISOLATED_WEB_APP_FROM_COMMAND_LINE_H_ @@ -28,6 +31,8 @@ class IsolatedWebAppUrlInfo; class WebAppProvider; +using MaybeInstallIsolatedWebAppCommandSuccess = + base::expected<InstallIsolatedWebAppCommandSuccess, std::string>; using MaybeIwaLocation = base::expected<absl::optional<IsolatedWebAppLocation>, std::string>; @@ -69,22 +74,25 @@ std::unique_ptr<ScopedProfileKeepAlive> optional_profile_keep_alive, base::TaskPriority task_priority); - void InstallIsolatedWebAppFromLocation( - std::unique_ptr<ScopedKeepAlive> keep_alive, - std::unique_ptr<ScopedProfileKeepAlive> optional_profile_keep_alive, - MaybeIwaLocation location, - base::OnceCallback<void( - base::expected<InstallIsolatedWebAppCommandSuccess, std::string>)> + void InstallIsolatedWebAppFromDevModeProxy( + const GURL& gurl, + base::OnceCallback<void(MaybeInstallIsolatedWebAppCommandSuccess)> callback); void OnReportInstallationResultForTesting( - base::RepeatingCallback<void( - base::expected<InstallIsolatedWebAppCommandSuccess, std::string>)> + base::RepeatingCallback<void(MaybeInstallIsolatedWebAppCommandSuccess)> on_report_installation_result) { on_report_installation_result_ = std::move(on_report_installation_result); } private: + void InstallIsolatedWebAppFromLocation( + std::unique_ptr<ScopedKeepAlive> keep_alive, + std::unique_ptr<ScopedProfileKeepAlive> optional_profile_keep_alive, + MaybeIwaLocation location, + base::OnceCallback<void(MaybeInstallIsolatedWebAppCommandSuccess)> + callback); + void OnGetIsolatedWebAppLocationFromCommandLine( std::unique_ptr<ScopedKeepAlive> keep_alive, std::unique_ptr<ScopedProfileKeepAlive> optional_profile_keep_alive, @@ -95,20 +103,18 @@ std::unique_ptr<ScopedKeepAlive> keep_alive, std::unique_ptr<ScopedProfileKeepAlive> optional_profile_keep_alive, const IsolatedWebAppLocation& location, - base::OnceCallback<void( - base::expected<InstallIsolatedWebAppCommandSuccess, std::string>)> + base::OnceCallback<void(MaybeInstallIsolatedWebAppCommandSuccess)> callback, base::expected<IsolatedWebAppUrlInfo, std::string> url_info); void OnInstallIsolatedWebApp( - base::OnceCallback<void( - base::expected<InstallIsolatedWebAppCommandSuccess, std::string>)> + base::OnceCallback<void(MaybeInstallIsolatedWebAppCommandSuccess)> callback, base::expected<InstallIsolatedWebAppCommandSuccess, InstallIsolatedWebAppCommandError> result); void ReportInstallationResult( - base::expected<InstallIsolatedWebAppCommandSuccess, std::string> result); + MaybeInstallIsolatedWebAppCommandSuccess result); raw_ref<Profile> profile_;
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc index 362e468..1df661a1 100644 --- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc +++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -1024,7 +1024,7 @@ transport_availability_.make_credential_attachment.has_value() && *transport_availability_.make_credential_attachment == device::AuthenticatorAttachment::kPlatform) { - v = transport_availability_.has_icloud_drive_enabled + v = has_icloud_drive_enabled_ ? MacOsHistogramValues:: kStartedCreateForProfileAuthenticatorICloudDriveEnabled : MacOsHistogramValues:: @@ -1056,7 +1056,7 @@ if (transport_availability_.request_type == device::FidoRequestType::kMakeCredential) { - v = transport_availability_.has_icloud_drive_enabled + v = has_icloud_drive_enabled_ ? MacOsHistogramValues:: kSuccessfulCreateForProfileAuthenticatorICloudDriveEnabled : MacOsHistogramValues:: @@ -1081,6 +1081,11 @@ is_active_profile_authenticator_user_ = is_active; } +void AuthenticatorRequestDialogModel::set_has_icloud_drive_enabled( + bool is_enabled) { + has_icloud_drive_enabled_ = is_enabled; +} + #endif base::WeakPtr<AuthenticatorRequestDialogModel>
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.h b/chrome/browser/webauthn/authenticator_request_dialog_model.h index 8501566..7415130 100644 --- a/chrome/browser/webauthn/authenticator_request_dialog_model.h +++ b/chrome/browser/webauthn/authenticator_request_dialog_model.h
@@ -632,6 +632,7 @@ void RecordMacOsSuccessHistogram(device::FidoRequestType, device::AuthenticatorType); void set_is_active_profile_authenticator_user(bool); + void set_has_icloud_drive_enabled(bool); #endif base::WeakPtr<AuthenticatorRequestDialogModel> GetWeakPtr(); @@ -853,6 +854,11 @@ // recently used the platform authenticator on macOS that saves credentials // into the profile. bool is_active_profile_authenticator_user_ = false; + + // has_icloud_drive_enabled_ is true if the current system has iCloud Drive + // enabled. This is used as an approximation for whether iCloud Keychain + // syncing is enabled. + bool has_icloud_drive_enabled_ = false; #endif base::WeakPtrFactory<AuthenticatorRequestDialogModel> weak_factory_{this};
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc index 96f070f..76d531c 100644 --- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc +++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
@@ -36,6 +36,7 @@ #include "chrome/browser/ui/page_action/page_action_icon_type.h" #include "chrome/browser/webauthn/authenticator_request_dialog_model.h" #include "chrome/browser/webauthn/cablev2_devices.h" +#include "chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.h" #include "chrome/browser/webauthn/passkey_model_factory.h" #include "chrome/browser/webauthn/webauthn_pref_names.h" #include "chrome/browser/webauthn/webauthn_switches.h" @@ -750,6 +751,8 @@ RequestSource::kWebAuthentication); #if BUILDFLAG(IS_MAC) + dialog_model_->set_has_icloud_drive_enabled(IsICloudDriveEnabled()); + if (base::FeatureList::IsEnabled(device::kWebAuthnICloudKeychain)) { const std::string& last_used = Profile::FromBrowserContext(GetBrowserContext())
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.h b/chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.h new file mode 100644 index 0000000..07dc12a --- /dev/null +++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.h
@@ -0,0 +1,13 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_WEBAUTHN_CHROME_AUTHENTICATOR_REQUEST_DELEGATE_MAC_H_ +#define CHROME_BROWSER_WEBAUTHN_CHROME_AUTHENTICATOR_REQUEST_DELEGATE_MAC_H_ + +// IsICloudDriveEnabled returns true if iCloud Drive is available. This is used +// as an approximation to "has iCloud Keychain enabled", which is what we would +// like to know but cannot easily learn. +bool IsICloudDriveEnabled(); + +#endif // CHROME_BROWSER_WEBAUTHN_CHROME_AUTHENTICATOR_REQUEST_DELEGATE_MAC_H_
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.mm b/chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.mm new file mode 100644 index 0000000..a267cde --- /dev/null +++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.mm
@@ -0,0 +1,11 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import <Foundation/Foundation.h> + +#include "chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.h" + +bool IsICloudDriveEnabled() { + return [NSFileManager defaultManager].ubiquityIdentityToken != nil; +}
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt index 6409a96..52f04a3 100644 --- a/chrome/build/android-arm32.pgo.txt +++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@ -chrome-android32-main-1692187073-27d5e7a5d982660cc26db9c4d19f00be1de0b850.profdata +chrome-android32-main-1692230285-1c2b0bd27e91cebb3aa1a9b3b73b92ba5f0f9412.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt index 9be56a11..b77ccfa4 100644 --- a/chrome/build/android-arm64.pgo.txt +++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@ -chrome-android64-main-1692187073-b9fd5ea5cbb5ae3a9015feb671d5d77c99753666.profdata +chrome-android64-main-1692230285-4665c16b6c3afab3a72f37ccc54af87fdf6cfce7.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt index 36c4d49..5f0fedc3 100644 --- a/chrome/build/linux.pgo.txt +++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@ -chrome-linux-main-1692165537-805d942258d9eb9b995cfe25a1eb848a4af7e1ed.profdata +chrome-linux-main-1692208772-f72bbe514e3208826cab8657a374058d08478817.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index 3a7c811b..782e31e 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1692200749-0088d0cad6a3b95958cdb3dba958cebf52038b3a.profdata +chrome-mac-arm-main-1692223043-7419dcd5041491b5553228cc441a1236c3f01245.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt index b9fddce..27124f9 100644 --- a/chrome/build/mac.pgo.txt +++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@ -chrome-mac-main-1692165537-4bc315e0e19754acee69f9b220b7578b50138439.profdata +chrome-mac-main-1692208772-8f9394328af5963dfed794bd09edf3c84cd35941.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index 907df9ec..d848dd5d 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1692197781-58d440ddc0a9fb270a21718ebbb899d1a3dbe3fe.profdata +chrome-win32-main-1692219491-ba095f155246cdb4f3aef9457fe545f9aae56539.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 7db8670..ee2ee22 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1692197781-91fbd65fb7246b090bc688c3faebdc98a2d16dce.profdata +chrome-win64-main-1692219491-0e555c5a1271c161980846ad252d6a93d5b00a93.profdata
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni index 36da4c24..59965719 100644 --- a/chrome/chrome_paks.gni +++ b/chrome/chrome_paks.gni
@@ -160,6 +160,7 @@ "$root_gen_dir/chrome/feed_resources.pak", "$root_gen_dir/chrome/feedback_resources.pak", "$root_gen_dir/chrome/gaia_auth_host_resources.pak", + "$root_gen_dir/chrome/hats_resources.pak", "$root_gen_dir/chrome/history_resources.pak", "$root_gen_dir/chrome/identity_internals_resources.pak", "$root_gen_dir/chrome/inline_login_resources.pak",
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json index dfb9407..f0c7b6c 100644 --- a/chrome/common/extensions/api/_api_features.json +++ b/chrome/common/extensions/api/_api_features.json
@@ -231,7 +231,7 @@ "matches": [ "chrome://version/*" ], - "platforms": ["chromeos"] + "platforms": ["chromeos", "lacros"] }], "chromeWebViewInternal": [{ "internal": true,
diff --git a/chrome/common/extensions/image_writer/image_writer_util_mac.cc b/chrome/common/extensions/image_writer/image_writer_util_mac.cc index a90d674..83d557d1 100644 --- a/chrome/common/extensions/image_writer/image_writer_util_mac.cc +++ b/chrome/common/extensions/image_writer/image_writer_util_mac.cc
@@ -8,8 +8,8 @@ #include <IOKit/IOBSD.h> #include <IOKit/storage/IOMedia.h> +#include "base/apple/mach_logging.h" #include "base/mac/foundation_util.h" -#include "base/mac/mach_logging.h" #include "base/mac/scoped_cftyperef.h" #include "base/mac/scoped_ioobject.h" #include "base/strings/sys_string_conversions.h"
diff --git a/chrome/common/ppapi_utils.cc b/chrome/common/ppapi_utils.cc index e143509..5c1ab0f 100644 --- a/chrome/common/ppapi_utils.cc +++ b/chrome/common/ppapi_utils.cc
@@ -8,6 +8,7 @@ #include "base/command_line.h" #include "build/build_config.h" #include "chrome/common/chrome_switches.h" +#include "content/public/common/content_switches.h" #include "ppapi/c/dev/ppb_audio_input_dev.h" #include "ppapi/c/dev/ppb_audio_output_dev.h" #include "ppapi/c/dev/ppb_buffer_dev.h" @@ -115,7 +116,36 @@ return false; } -bool IsNaclAllowed() { +namespace { +bool g_allow_nacl = true; + +bool IsBrowserProcess() { return !base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableNaCl); + switches::kProcessType); +} // namespace +} // namespace + +void DisallowNacl() { + CHECK(IsBrowserProcess()); + g_allow_nacl = false; +} + +bool IsNaclAllowed() { + // In the browser process we directly check g_allow_nacl and in other + // processes we check for the command line switch. This is because: + // (1) It's discouraged to add switches to browser process. + // (2) We must use a command line switch for child processes to get the + // information early in startup. + if (IsBrowserProcess()) { + return g_allow_nacl; + } else { + return !base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableNaCl); + } +} + +void AppendDisableNaclSwitchIfNecessary(base::CommandLine* command_line) { + if (!IsNaclAllowed()) { + command_line->AppendSwitch(switches::kDisableNaCl); + } }
diff --git a/chrome/common/ppapi_utils.h b/chrome/common/ppapi_utils.h index 85be6d44..50afafa90 100644 --- a/chrome/common/ppapi_utils.h +++ b/chrome/common/ppapi_utils.h
@@ -5,12 +5,24 @@ #ifndef CHROME_COMMON_PPAPI_UTILS_H_ #define CHROME_COMMON_PPAPI_UTILS_H_ +namespace base { +class CommandLine; +} // namespace base + // Returns true if the interface name passed in is supported by the // browser. bool IsSupportedPepperInterface(const char* name); -// Returns whether any NaCl usage is allowed in Chrome. This checks command-line -// flags since this can be called from non-browser processes. +// Must be called from the browser process. If not called then NaCl is allowed. +// Once it is called NaCl is disallowed in all processes. +void DisallowNacl(); + +// Returns whether any NaCl usage is allowed in Chrome. This has a different +// implementation for browser and non-browser processes but the return value +// should be identical. bool IsNaclAllowed(); +// Adds the kDisableNacl command line flag if disable is disallowed. +void AppendDisableNaclSwitchIfNecessary(base::CommandLine* command_line); + #endif // CHROME_COMMON_PPAPI_UTILS_H_
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc index a703b26..3cf38f5 100644 --- a/chrome/common/webui_url_constants.cc +++ b/chrome/common/webui_url_constants.cc
@@ -225,6 +225,10 @@ const char kChromeUIThemeURL[] = "chrome://theme/"; const char kChromeUITranslateInternalsHost[] = "translate-internals"; const char kChromeUITopChromeDomain[] = "top-chrome"; +#if !BUILDFLAG(IS_ANDROID) +const char kChromeUIUntrustedHatsHost[] = "hats"; +const char kChromeUIUntrustedHatsURL[] = "chrome-untrusted://hats/"; +#endif // !BUILDFLAG(IS_ANDROID) const char kChromeUIUntrustedImageEditorURL[] = "chrome-untrusted://image-editor/"; const char kChromeUIUntrustedPrintURL[] = "chrome-untrusted://print/";
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h index 1cfb396..ce0e4a4 100644 --- a/chrome/common/webui_url_constants.h +++ b/chrome/common/webui_url_constants.h
@@ -219,6 +219,10 @@ extern const char kChromeUIThemeURL[]; extern const char kChromeUITopChromeDomain[]; extern const char kChromeUITranslateInternalsHost[]; +#if !BUILDFLAG(IS_ANDROID) +extern const char kChromeUIUntrustedHatsHost[]; +extern const char kChromeUIUntrustedHatsURL[]; +#endif // !BUILDFLAG(IS_ANDROID) extern const char kChromeUIUntrustedImageEditorURL[]; extern const char kChromeUIUntrustedPrintURL[]; extern const char kChromeUIUntrustedThemeURL[];
diff --git a/chrome/renderer/accessibility/read_anything_app_controller.cc b/chrome/renderer/accessibility/read_anything_app_controller.cc index 295b771..0f4baff 100644 --- a/chrome/renderer/accessibility/read_anything_app_controller.cc +++ b/chrome/renderer/accessibility/read_anything_app_controller.cc
@@ -595,6 +595,12 @@ &ReadAnythingAppController::LooseLineSpacing) .SetProperty("veryLooseLineSpacing", &ReadAnythingAppController::VeryLooseLineSpacing) + .SetProperty("standardLetterSpacing", + &ReadAnythingAppController::StandardLetterSpacing) + .SetProperty("wideLetterSpacing", + &ReadAnythingAppController::WideLetterSpacing) + .SetProperty("veryWideLetterSpacing", + &ReadAnythingAppController::VeryWideLetterSpacing) .SetProperty("isWebUIToolbarVisible", &ReadAnythingAppController::IsWebUIToolbarEnabled) .SetProperty("isSelectable", &ReadAnythingAppController::IsSelectable) @@ -609,6 +615,8 @@ .SetMethod("isOverline", &ReadAnythingAppController::IsOverline) .SetMethod("onConnected", &ReadAnythingAppController::OnConnected) .SetMethod("onCopy", &ReadAnythingAppController::OnCopy) + .SetMethod("onFontSizeChanged", + &ReadAnythingAppController::OnFontSizeChanged) .SetMethod("onScroll", &ReadAnythingAppController::OnScroll) .SetMethod("onLinkClicked", &ReadAnythingAppController::OnLinkClicked) .SetMethod("onStandardLineSpacing", @@ -617,8 +625,22 @@ &ReadAnythingAppController::OnLooseLineSpacing) .SetMethod("onVeryLooseLineSpacing", &ReadAnythingAppController::OnVeryLooseLineSpacing) + .SetMethod("onStandardLetterSpacing", + &ReadAnythingAppController::OnStandardLetterSpacing) + .SetMethod("onWideLetterSpacing", + &ReadAnythingAppController::OnWideLetterSpacing) + .SetMethod("onVeryWideLetterSpacing", + &ReadAnythingAppController::OnVeryWideLetterSpacing) + .SetMethod("onLightTheme", &ReadAnythingAppController::OnLightTheme) + .SetMethod("onDefaultTheme", &ReadAnythingAppController::OnDefaultTheme) + .SetMethod("onDarkTheme", &ReadAnythingAppController::OnDarkTheme) + .SetMethod("onYellowTheme", &ReadAnythingAppController::OnYellowTheme) + .SetMethod("onBlueTheme", &ReadAnythingAppController::OnBlueTheme) + .SetMethod("onFontChange", &ReadAnythingAppController::OnFontChange) .SetMethod("getLineSpacingValue", &ReadAnythingAppController::GetLineSpacingValue) + .SetMethod("getLetterSpacingValue", + &ReadAnythingAppController::GetLetterSpacingValue) .SetMethod("onSelectionChange", &ReadAnythingAppController::OnSelectionChange) .SetMethod("setContentForTesting", @@ -676,13 +698,27 @@ int ReadAnythingAppController::StandardLineSpacing() { return static_cast<int>(read_anything::mojom::LineSpacing::kStandard); } + int ReadAnythingAppController::LooseLineSpacing() { return static_cast<int>(read_anything::mojom::LineSpacing::kLoose); } + int ReadAnythingAppController::VeryLooseLineSpacing() { return static_cast<int>(read_anything::mojom::LineSpacing::kVeryLoose); } +int ReadAnythingAppController::StandardLetterSpacing() { + return static_cast<int>(read_anything::mojom::LetterSpacing::kStandard); +} + +int ReadAnythingAppController::WideLetterSpacing() { + return static_cast<int>(read_anything::mojom::LetterSpacing::kWide); +} + +int ReadAnythingAppController::VeryWideLetterSpacing() { + return static_cast<int>(read_anything::mojom::LetterSpacing::kVeryWide); +} + std::vector<ui::AXNodeID> ReadAnythingAppController::GetChildren( ui::AXNodeID ax_node_id) const { std::vector<ui::AXNodeID> child_ids; @@ -811,6 +847,16 @@ page_handler_->OnCopy(); } +void ReadAnythingAppController::OnFontSizeChanged(bool increase) { + if (increase) { + model_.IncreaseTextSize(); + } else { + model_.DecreaseTextSize(); + } + + // TODO(crbug.com/1465029) save font size prefs +} + void ReadAnythingAppController::OnScroll(bool on_selection) const { model_.OnScroll(on_selection, /* from_reading_mode= */ true); } @@ -841,6 +887,45 @@ read_anything::mojom::LineSpacing::kVeryLoose); } +void ReadAnythingAppController::OnStandardLetterSpacing() { + page_handler_->OnLetterSpaceChange( + read_anything::mojom::LetterSpacing::kStandard); +} + +void ReadAnythingAppController::OnWideLetterSpacing() { + page_handler_->OnLetterSpaceChange( + read_anything::mojom::LetterSpacing::kWide); +} + +void ReadAnythingAppController::OnVeryWideLetterSpacing() { + page_handler_->OnLetterSpaceChange( + read_anything::mojom::LetterSpacing::kVeryWide); +} + +void ReadAnythingAppController::OnLightTheme() { + page_handler_->OnColorChange(read_anything::mojom::Colors::kLight); +} + +void ReadAnythingAppController::OnDefaultTheme() { + page_handler_->OnColorChange(read_anything::mojom::Colors::kDefault); +} + +void ReadAnythingAppController::OnDarkTheme() { + page_handler_->OnColorChange(read_anything::mojom::Colors::kDark); +} + +void ReadAnythingAppController::OnYellowTheme() { + page_handler_->OnColorChange(read_anything::mojom::Colors::kYellow); +} + +void ReadAnythingAppController::OnBlueTheme() { + page_handler_->OnColorChange(read_anything::mojom::Colors::kYellow); +} + +void ReadAnythingAppController::OnFontChange(const std::string& font) { + page_handler_->OnFontChange(font); +} + double ReadAnythingAppController::GetLineSpacingValue(int line_spacing) const { if (line_spacing > static_cast<int>(read_anything::mojom::LineSpacing::kMaxValue)) { @@ -852,6 +937,18 @@ static_cast<read_anything::mojom::LineSpacing>(line_spacing)); } +double ReadAnythingAppController::GetLetterSpacingValue( + int letter_spacing) const { + if (letter_spacing > + static_cast<int>(read_anything::mojom::LetterSpacing::kMaxValue)) { + return model_.GetLetterSpacingValue( + read_anything::mojom::LetterSpacing::kDefaultValue); + } + + return model_.GetLetterSpacingValue( + static_cast<read_anything::mojom::LetterSpacing>(letter_spacing)); +} + void ReadAnythingAppController::OnSelectionChange(ui::AXNodeID anchor_node_id, int anchor_offset, ui::AXNodeID focus_node_id,
diff --git a/chrome/renderer/accessibility/read_anything_app_controller.h b/chrome/renderer/accessibility/read_anything_app_controller.h index b8eb9143..f3c6969 100644 --- a/chrome/renderer/accessibility/read_anything_app_controller.h +++ b/chrome/renderer/accessibility/read_anything_app_controller.h
@@ -105,12 +105,16 @@ SkColor BackgroundColor() const; std::string FontName() const; float FontSize() const; + void OnFontSizeChanged(bool increase); SkColor ForegroundColor() const; float LetterSpacing() const; float LineSpacing() const; int StandardLineSpacing(); int LooseLineSpacing(); int VeryLooseLineSpacing(); + int StandardLetterSpacing(); + int WideLetterSpacing(); + int VeryWideLetterSpacing(); std::vector<ui::AXNodeID> GetChildren(ui::AXNodeID ax_node_id) const; std::string GetHtmlTag(ui::AXNodeID ax_node_id) const; std::string GetLanguage(ui::AXNodeID ax_node_id) const; @@ -132,7 +136,17 @@ void OnStandardLineSpacing(); void OnLooseLineSpacing(); void OnVeryLooseLineSpacing(); + void OnStandardLetterSpacing(); + void OnWideLetterSpacing(); + void OnVeryWideLetterSpacing(); + void OnLightTheme(); + void OnDefaultTheme(); + void OnDarkTheme(); + void OnYellowTheme(); + void OnBlueTheme(); + void OnFontChange(const std::string& font); double GetLineSpacingValue(int line_spacing) const; + double GetLetterSpacingValue(int letter_spacing) const; void Distill(); void Draw();
diff --git a/chrome/renderer/accessibility/read_anything_app_model.cc b/chrome/renderer/accessibility/read_anything_app_model.cc index da8aad6..600e584 100644 --- a/chrome/renderer/accessibility/read_anything_app_model.cc +++ b/chrome/renderer/accessibility/read_anything_app_model.cc
@@ -707,3 +707,17 @@ } } } + +void ReadAnythingAppModel::IncreaseTextSize() { + font_size_ += kReadAnythingFontScaleIncrement; + if (font_size_ > kReadAnythingMaximumFontScale) { + font_size_ = kReadAnythingMaximumFontScale; + } +} + +void ReadAnythingAppModel::DecreaseTextSize() { + font_size_ -= kReadAnythingFontScaleIncrement; + if (font_size_ < kReadAnythingMinimumFontScale) { + font_size_ = kReadAnythingMinimumFontScale; + } +}
diff --git a/chrome/renderer/accessibility/read_anything_app_model.h b/chrome/renderer/accessibility/read_anything_app_model.h index 239465b5..b5a40d0 100644 --- a/chrome/renderer/accessibility/read_anything_app_model.h +++ b/chrome/renderer/accessibility/read_anything_app_model.h
@@ -138,12 +138,15 @@ double GetLineSpacingValue( read_anything::mojom::LineSpacing line_spacing) const; + double GetLetterSpacingValue( + read_anything::mojom::LetterSpacing letter_spacing) const; + + void IncreaseTextSize(); + void DecreaseTextSize(); private: void EraseTree(ui::AXTreeID tree_id); - double GetLetterSpacingValue( - read_anything::mojom::LetterSpacing letter_spacing) const; void InsertDisplayNode(ui::AXNodeID node); void ResetSelection(); void InsertSelectionNode(ui::AXNodeID node);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 4d71135..3305771d 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -2634,7 +2634,7 @@ sources += [ "../browser/ui/views/device_signals_consent/consent_dialog_browsertest.cc" ] } - if (!is_fuchsia) { + if (!is_fuchsia && enable_nacl) { sources += [ "../browser/chrome_browser_main_extra_parts_nacl_deprecation_browsertest.cc" ] } @@ -9786,6 +9786,8 @@ "../browser/ui/views/autofill/payments/card_unmask_prompt_view_tester_views.h", "../browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.cc", "../browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.h", + "../browser/ui/views/create_application_shortcut_view_test_support.cc", + "../browser/ui/views/create_application_shortcut_view_test_support.h", "../browser/ui/views/tabs/tab_hover_card_test_util.cc", "../browser/ui/views/tabs/tab_hover_card_test_util.h", "../browser/ui/views/web_apps/web_app_integration_test_driver.cc",
diff --git a/chrome/test/base/chrome_test_suite.cc b/chrome/test/base/chrome_test_suite.cc index 6fdac11..6866fdc0 100644 --- a/chrome/test/base/chrome_test_suite.cc +++ b/chrome/test/base/chrome_test_suite.cc
@@ -41,7 +41,7 @@ #if BUILDFLAG(IS_MAC) #include "base/apple/bundle_locations.h" -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "chrome/browser/chrome_browser_application_mac.h" #endif @@ -71,7 +71,7 @@ void ChromeTestSuite::Initialize() { #if BUILDFLAG(IS_MAC) - base::mac::ScopedNSAutoreleasePool autorelease_pool; + base::apple::ScopedNSAutoreleasePool autorelease_pool; chrome_browser_application_mac::RegisterBrowserCrApp(); #endif
diff --git a/chrome/test/base/in_process_browser_test.cc b/chrome/test/base/in_process_browser_test.cc index fa8dc54..5b85dab 100644 --- a/chrome/test/base/in_process_browser_test.cc +++ b/chrome/test/base/in_process_browser_test.cc
@@ -94,7 +94,7 @@ #include "ui/base/ui_base_features.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "chrome/test/base/scoped_bundle_swizzler_mac.h" #include "services/device/public/cpp/test/fake_geolocation_manager.h" #endif @@ -854,7 +854,7 @@ // deallocation via an autorelease pool (such as browser window closure and // browser shutdown). To avoid this, the following pool is recycled after each // time code is directly executed. - autorelease_pool_ = new base::mac::ScopedNSAutoreleasePool; + autorelease_pool_ = new base::apple::ScopedNSAutoreleasePool; #endif // Pump any pending events that were created as a result of creating a
diff --git a/chrome/test/base/in_process_browser_test.h b/chrome/test/base/in_process_browser_test.h index b0508b4..feb4c48 100644 --- a/chrome/test/base/in_process_browser_test.h +++ b/chrome/test/base/in_process_browser_test.h
@@ -35,9 +35,9 @@ class CommandLine; #if BUILDFLAG(IS_MAC) -namespace mac { +namespace apple { class ScopedNSAutoreleasePool; -} // namespace mac +} // namespace apple #endif // BUILDFLAG(IS_MAC) #if BUILDFLAG(IS_WIN) @@ -345,7 +345,7 @@ #if BUILDFLAG(IS_MAC) // Returns the autorelease pool in use inside RunTestOnMainThreadLoop(). - base::mac::ScopedNSAutoreleasePool* AutoreleasePool() const { + base::apple::ScopedNSAutoreleasePool* AutoreleasePool() const { return autorelease_pool_; } #endif // BUILDFLAG(IS_MAC) @@ -433,7 +433,7 @@ feature_engagement::test::ScopedIphFeatureList block_all_iph_feature_list_; #if BUILDFLAG(IS_MAC) - raw_ptr<base::mac::ScopedNSAutoreleasePool, DanglingUntriaged> + raw_ptr<base::apple::ScopedNSAutoreleasePool, DanglingUntriaged> autorelease_pool_ = nullptr; std::unique_ptr<ScopedBundleSwizzlerMac> bundle_swizzler_;
diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc index 0c555cc5..ec77b8f 100644 --- a/chrome/test/chromedriver/server/http_handler.cc +++ b/chrome/test/chromedriver/server/http_handler.cc
@@ -49,7 +49,7 @@ #include "url/url_util.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif const char kCreateWebSocketPath[] = @@ -229,7 +229,7 @@ url_base_(url_base), received_shutdown_(false) { #if BUILDFLAG(IS_MAC) - base::mac::ScopedNSAutoreleasePool autorelease_pool; + base::apple::ScopedNSAutoreleasePool autorelease_pool; #endif context_getter_ = new URLRequestContextGetter(io_task_runner_); socket_factory_ = CreateSyncWebSocketFactory(context_getter_.get());
diff --git a/chrome/test/data/variations/crash_seed.json b/chrome/test/data/variations/crash_seed.json index 9701e0a..38130c3f 100644 --- a/chrome/test/data/variations/crash_seed.json +++ b/chrome/test/data/variations/crash_seed.json
@@ -1 +1,4 @@ -{"variations_compressed_seed":"H4sIAAAAAAAA/4zMzU7CMADAcb78WjQSjp4WCEljRMfWje3ggaAzEFoyVwLzYjpWaKFsOiBkPpC+hScfwEcyIT7A7v//T/F91BPtJJjiZPjRb6MnlwfkReLJWEd638RLr42WCGIy00YEZWg9NgLiaehhBQPi7fHS87ui222spXjPwhWW9v6+9lVSznsp3fCsl8RzsbCLA1u5eIxpKFk0pLt4xqtR2FQabpLOmCuYjEgqqPTZdvd2GN0kJWyzFfFiMFFuDlk0il9z9NVC2LzKBU+V1j88n+eV63nkZ1CrONbttVpQi2pJLYMCKIISKIMzUAFH4BicghP+8/37eVlvcbrhd7qpUUotZjLLCB0Lmrphmx3oGLrOOhRCzbEp1Cz4FwAA//+p+r1+rgEAAA==","variations_seed_signature":"MEYCIQD9Q4lqLn7LFnPfOHSSrVLw8lyy+LtfLsdDST/P+qVeLwIhAKPXJ79k4UX6WXXHw3IRb4tCMcyTlX/sql9ATZW4ntF2"} \ No newline at end of file +{ + "variations_compressed_seed": "H4sIAAAAAAAA/4zMTU7yQACAYX4+PrXRSFi6aiAkoxGdgRbKwkWhgBJmGmwNPxsz02mZobU1BWLwQMZTuPIAHsmEeIDu3/dRHAf3JUoWc5JMmssQWwPddnmI33shGU2RbXkQW0/acj14wy9jac9ISNylwKMHjazN1mI9dUxpmrXmFLHonuytHryrfBaU035KN2LfT+JAroz82FDOBjFlkc8ndBd7osxZXakNk9Tzh9KPuJtKGjn+dvd6GIdJ6vqbrYxX45lyfci4HT9n6Ms5Vr/IBM+Vxh8cBFnlahb58bJSQhDeXKk5Na8W1CLIgTwogCI4Af9ACfwHx+BIfH/9fJxXG4JuxG3AIep2O7wFucZ1o6NB3QgQbDLW9bjeppAZbdhh2m8AAAD///hDTY2vAQAA", + "variations_seed_signature": "MEYCIQCOmQ9hK/GYBGiWcNnEyX5a0rVH1jtasy9aqwnzsTTdcQIhALk50waBs43eV2vCMECoMpD/bx8VRBFVDC86tF1EeIQx" +} \ No newline at end of file
diff --git a/chrome/test/data/variations/variations_seed b/chrome/test/data/variations/variations_seed deleted file mode 100644 index d0fc4482..0000000 --- a/chrome/test/data/variations/variations_seed +++ /dev/null
@@ -1,4 +0,0 @@ -{ - "variations_compressed_seed": "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", - "variations_seed_signature": "MEYCIQCuSzd+2tjoodEgDRBI+EulRcFPOEepSO2+dGE9VyKQAgIhANIKCfuzryFBs0uD72RTS56X8oJrfhWkWVlpy4SqRn4l" -} \ No newline at end of file
diff --git a/chrome/test/data/variations/variations_seed.json b/chrome/test/data/variations/variations_seed.json new file mode 100644 index 0000000..1747dd3 --- /dev/null +++ b/chrome/test/data/variations/variations_seed.json
@@ -0,0 +1,4 @@ +{ + "variations_compressed_seed": "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", + "variations_seed_signature": "MEYCIQCarXFrwfOEsByXIlcTAdFhOY/2uuFnJwwnymHvcaswvgIhAMDIv3Z4vamdiqfhzFhNSLgexsQCisw5X2BPkpUaiwR2" +} \ No newline at end of file
diff --git a/chrome/test/data/web_apps/tab_strip_customizations.html b/chrome/test/data/web_apps/tab_strip_customizations.html index a5a9710a..0d0c9079 100644 --- a/chrome/test/data/web_apps/tab_strip_customizations.html +++ b/chrome/test/data/web_apps/tab_strip_customizations.html
@@ -3,7 +3,6 @@ <head> <link rel="manifest" href="tab_strip_customizations.json"> <link rel="icon" href="basic-48.png"> - <title>Tab Strip Customizations</title> </head> <body> <h1>Web app with tab strip</h1>
diff --git a/chrome/test/data/webui/chromeos/personalization_app/ambient_preview_small_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/ambient_preview_small_element_test.ts index 64a0c3a..33214f4 100644 --- a/chrome/test/data/webui/chromeos/personalization_app/ambient_preview_small_element_test.ts +++ b/chrome/test/data/webui/chromeos/personalization_app/ambient_preview_small_element_test.ts
@@ -84,6 +84,48 @@ assertEquals(null, container.querySelector('#imageContainer')); }); + test('shows placeholders while waiting for assets to load', async () => { + // Only AmbientModeEnabled is set. + personalizationStore.data.ambient.ambientModeEnabled = true; + // Null indicates that albums have not yet loaded. + personalizationStore.data.ambient.albums = null; + ambientPreviewSmallElement = initElement(AmbientPreviewSmall); + personalizationStore.notifyObservers(); + await waitAfterNextRender(ambientPreviewSmallElement); + + const container = ambientPreviewSmallElement.$.container; + assertEquals('imagePlaceholder', container.firstElementChild?.id); + + const textPlaceholder = container.querySelector('#textPlaceholder'); + assertTrue(!!textPlaceholder, 'textPlaceholder element exists'); + for (const child of textPlaceholder.children) { + assertTrue( + child.classList.contains('placeholder'), + 'every element has placeholder class'); + } + + assertEquals(null, container.querySelector('#imageContainer')); + }); + + test('ends loading early if ambient mode is disabled', async () => { + personalizationStore.data.ambient.ambientModeEnabled = false; + ambientPreviewSmallElement = initElement(AmbientPreviewSmall); + personalizationStore.notifyObservers(); + await waitAfterNextRender(ambientPreviewSmallElement); + + const zeroStateTextContainer = + ambientPreviewSmallElement.shadowRoot!.getElementById( + 'zeroStateTextContainer'); + assertTrue(!!zeroStateTextContainer); + const textSpan = + zeroStateTextContainer.firstElementChild as HTMLSpanElement; + assertTrue(!!textSpan); + assertEquals('span', textSpan.tagName.toLowerCase()); + assertEquals( + ambientPreviewSmallElement.i18n('ambientModeMainPageZeroStateMessage'), + textSpan.innerText.trim()); + }); + test('shows image when loaded', async () => { personalizationStore.data.ambient.albums = ambientProvider.albums; personalizationStore.data.ambient.topicSource = TopicSource.kArtGallery;
diff --git a/chrome/test/data/webui/downloads/item_test.ts b/chrome/test/data/webui/downloads/item_test.ts index f92634e..b8116f6 100644 --- a/chrome/test/data/webui/downloads/item_test.ts +++ b/chrome/test/data/webui/downloads/item_test.ts
@@ -116,8 +116,12 @@ assertTrue(item.$['file-icon'].hidden); }); - test('open now button controlled by load time data', async () => { - loadTimeData.overrideValues({'allowOpenNow': true}); + test('open now button allowed by load time data', async () => { + loadTimeData.overrideValues( + {'allowOpenNow': true, 'updateDeepScanningUX': false}); + const item = document.createElement('downloads-item'); + document.body.innerHTML = window.trustedTypes!.emptyHTML; + document.body.appendChild(item); item.set('data', createDownload({ filePath: 'unique1', hideDate: false, @@ -125,8 +129,14 @@ })); flush(); assertNotEquals(item.shadowRoot!.querySelector('#openNow'), null); + }); - loadTimeData.overrideValues({'allowOpenNow': false}); + test('open now button forbidden by load time data', async () => { + loadTimeData.overrideValues( + {'allowOpenNow': false, 'updateDeepScanningUX': false}); + const item = document.createElement('downloads-item'); + document.body.innerHTML = window.trustedTypes!.emptyHTML; + document.body.appendChild(item); item.set('data', createDownload({ filePath: 'unique1', hideDate: false,
diff --git a/chrome/test/data/webui/engagement/site_engagement_browsertest.js b/chrome/test/data/webui/engagement/site_engagement_browsertest.js index 17a2c44..fe5ab1c 100644 --- a/chrome/test/data/webui/engagement/site_engagement_browsertest.js +++ b/chrome/test/data/webui/engagement/site_engagement_browsertest.js
@@ -5,8 +5,8 @@ /** * @fileoverview Test suite for the Site Engagement WebUI. */ -var EXAMPLE_URL_1 = 'http://example.com/'; -var EXAMPLE_URL_2 = 'http://shmlexample.com/'; +const EXAMPLE_URL_1 = 'http://example.com/'; +const EXAMPLE_URL_2 = 'http://shmlexample.com/'; GEN('#include "components/site_engagement/content/site_engagement_service.h"'); GEN('#include "chrome/browser/engagement/site_engagement_service_factory.h"'); @@ -35,29 +35,21 @@ '//third_party/mocha/mocha.js', '//chrome/test/data/webui/mocha_adapter.js', ], - - /** @override */ - setUp: function() { - testing.Test.prototype.setUp.call(this); - suiteSetup(async function() { - await whenPageIsPopulatedForTest(); - await disableAutoupdateForTests(); - }); - }, }; TEST_F('SiteEngagementBrowserTest', 'All', function() { - var cells; + let app; + let cells; function getCells() { - var originCells = - Array.from(document.getElementsByClassName('origin-cell')); - var scoreInputs = - Array.from(document.getElementsByClassName('base-score-input')); - var bonusScoreCells = - Array.from(document.getElementsByClassName('bonus-score-cell')); - var totalScoreCells = - Array.from(document.getElementsByClassName('total-score-cell')); + const originCells = + Array.from(app.shadowRoot.querySelectorAll('.origin-cell')); + const scoreInputs = + Array.from(app.shadowRoot.querySelectorAll('.base-score-input')); + const bonusScoreCells = + Array.from(app.shadowRoot.querySelectorAll('.bonus-score-cell')); + const totalScoreCells = + Array.from(app.shadowRoot.querySelectorAll('.total-score-cell')); return originCells.map((c, i) => { return { origin: c, @@ -70,6 +62,11 @@ setup(async function() { await import('chrome://webui-test/mojo_webui_test_support.js'); + document.body.innerHTML = window.trustedTypes.emptyHTML; + app = document.createElement('site-engagement-app'); + document.body.appendChild(app); + await app.whenPopulatedForTest(); + app.disableAutoupdate(); cells = getCells(); }); @@ -86,11 +83,12 @@ }); test('change score', async function() { - var firstRow = cells[0]; + const firstRow = cells[0]; firstRow.scoreInput.value = 50; firstRow.scoreInput.dispatchEvent(new Event('change')); - const {info} = await engagementDetailsProvider.getSiteEngagementDetails(); + const {info} = + await app.engagementDetailsProvider.getSiteEngagementDetails(); assertEquals(firstRow.origin.textContent, info[0].origin.url); assertEquals(50, info[0].baseScore); });
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn index aa9a632..694563e 100644 --- a/chrome/test/data/webui/settings/chromeos/BUILD.gn +++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -239,6 +239,8 @@ "os_people_page/test_fingerprint_browser_proxy.ts", "os_people_page/test_os_sync_browser_proxy.ts", + "os_printing_page/cups_print_server_test.ts", + "os_printing_page/cups_printer_dialog_test.ts", "os_printing_page/cups_printer_landing_page_test.ts", "os_printing_page/cups_printer_page_test.ts", "os_printing_page/cups_printer_test_utils.ts",
diff --git a/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_print_server_test.ts b/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_print_server_test.ts new file mode 100644 index 0000000..70111ff --- /dev/null +++ b/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_print_server_test.ts
@@ -0,0 +1,284 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {AddPrintServerDialogElement, CupsPrinterInfo, CupsPrintersBrowserProxyImpl, CupsPrintersEntryManager, PrinterDialogErrorElement, PrinterListEntry, PrinterType, PrintServerResult, SettingsCupsAddPrinterDialogElement, SettingsCupsPrintersElement} from 'chrome://os-settings/lazy_load.js'; +import {CrInputElement, CrToastElement, Router, routes} from 'chrome://os-settings/os_settings.js'; +import {webUIListenerCallback} from 'chrome://resources/ash/common/cr.m.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; +import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; +import {flushTasks} from 'chrome://webui-test/polymer_test_util.js'; + +import {createCupsPrinterInfo, createPrinterListEntry} from './cups_printer_test_utils.js'; +import {TestCupsPrintersBrowserProxy} from './test_cups_printers_browser_proxy.js'; + +suite('PrintServerTests', () => { + let page: SettingsCupsPrintersElement; + let dialog: SettingsCupsAddPrinterDialogElement; + let entryManager: CupsPrintersEntryManager; + let cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy; + + setup(async () => { + entryManager = CupsPrintersEntryManager.getInstance(); + setEntryManagerPrinters( + /*savedPrinters=*/[], /*automaticPrinters=*/[], + /*discoveredPrinters=*/[], /*printServerPrinters=*/[]); + + cupsPrintersBrowserProxy = new TestCupsPrintersBrowserProxy(); + + CupsPrintersBrowserProxyImpl.setInstanceForTesting( + cupsPrintersBrowserProxy); + + Router.getInstance().navigateTo(routes.CUPS_PRINTERS); + + page = document.createElement('settings-cups-printers'); + document.body.appendChild(page); + assertTrue(!!page); + const element = + page.shadowRoot!.querySelector('settings-cups-add-printer-dialog'); + assertTrue(!!element); + dialog = element; + + await flushTasks(); + }); + + teardown(() => { + cupsPrintersBrowserProxy.reset(); + page.remove(); + dialog.remove(); + }); + + function setEntryManagerPrinters( + savedPrinters: PrinterListEntry[], automaticPrinters: CupsPrinterInfo[], + discoveredPrinters: CupsPrinterInfo[], + printerServerPrinters: PrinterListEntry[]): void { + entryManager.setSavedPrintersList(savedPrinters); + entryManager.setNearbyPrintersList(automaticPrinters, discoveredPrinters); + entryManager.printServerPrinters = printerServerPrinters; + } + + /** + * Returns the print server dialog if it is available. + */ + function getPrintServerDialog(page: SettingsCupsPrintersElement): + AddPrintServerDialogElement { + assertTrue(!!page); + const element = + page.shadowRoot!.querySelector('settings-cups-add-printer-dialog'); + assertTrue(!!element); + dialog = element; + const addDialog = + dialog.shadowRoot!.querySelector('add-print-server-dialog'); + assertTrue(!!addDialog); + return addDialog; + } + + /** + * Opens the add print server dialog, inputs |address| with the specified + * |error|. Adds the print server and returns a promise for handling the add + * event. + * The promise returned when queryPrintServer is called. + */ + async function addPrintServer(address: string, error: number): Promise<void> { + // Open the add manual printe dialog. + assertTrue(!!page); + dialog.open(); + flush(); + + const addPrinterDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addPrinterDialog); + // Switch to Add print server dialog. + let button = addPrinterDialog.shadowRoot!.querySelector<HTMLButtonElement>( + '#print-server-button'); + assertTrue(!!button); + button.click(); + flush(); + const printServerDialog = + dialog.shadowRoot!.querySelector('add-print-server-dialog'); + assertTrue(!!printServerDialog); + + flush(); + cupsPrintersBrowserProxy.setQueryPrintServerResult(error); + await flushTasks(); + // Fill dialog with the server address. + const printServerAddressInput = + printServerDialog.shadowRoot!.querySelector<HTMLInputElement>( + '#printServerAddressInput'); + assertTrue(!!printServerAddressInput); + printServerAddressInput.value = address; + // Add the print server. + button = printServerDialog.shadowRoot!.querySelector<HTMLButtonElement>( + '.action-button'); + assertTrue(!!button); + // Button should not be disabled before clicking on it. + assertFalse(button.disabled); + button.click(); + // Clicking on the button should disable it. + assertTrue(button.disabled); + await cupsPrintersBrowserProxy.whenCalled('queryPrintServer'); + } + + function verifyErrorMessage(expectedError: string): void { + // Assert that the dialog did not close on errors. + const printServerDialog = getPrintServerDialog(page); + const dialogError = + printServerDialog.shadowRoot!.querySelector<PrinterDialogErrorElement>( + '#server-dialog-error'); + assertTrue(!!dialogError); + // Assert that the dialog error is displayed. + assertFalse(dialogError.hidden); + assertEquals( + loadTimeData.getString(expectedError), dialogError.get('errorText')); + } + + function verifyToastMessage( + expectedMessage: string, numPrinters: number): void { + // We always display the total number of printers found from a print + // server. + const toast = page.shadowRoot!.querySelector<CrToastElement>( + '#printServerErrorToast'); + assertTrue(!!toast); + assertTrue(toast.open); + assertEquals( + loadTimeData.getStringF(expectedMessage, numPrinters), + toast.textContent?.trim()); + } + + test('AddPrintServerIsSuccessful', async () => { + // Initialize the return result from adding a print server. + cupsPrintersBrowserProxy.printServerPrinters = { + printerList: [ + createCupsPrinterInfo('nameA', 'serverAddress', 'idA'), + createCupsPrinterInfo('nameB', 'serverAddress', 'idB'), + ], + }; + await addPrintServer('serverAddress', PrintServerResult.NO_ERRORS); + flush(); + verifyToastMessage('printServerFoundManyPrinters', /*numPrinters=*/ 2); + assertEquals(2, entryManager.printServerPrinters.length); + }); + + test('HandleDuplicateQueries', async () => { + // Initialize the return result from adding a print server. + cupsPrintersBrowserProxy.printServerPrinters = { + printerList: [ + createCupsPrinterInfo('nameA', 'serverAddress', 'idA'), + createCupsPrinterInfo('nameB', 'serverAddress', 'idB'), + ], + }; + + await flushTasks(); + // Simulate that a print server was queried previously. + setEntryManagerPrinters( + /*savedPrinters=*/[], /*nearbyPrinters=*/[], + /*discoveredPrinters=*/[], [ + createPrinterListEntry( + 'nameA', 'serverAddress', 'idA', PrinterType.PRINTSERVER), + createPrinterListEntry( + 'nameB', 'serverAddress', 'idB', PrinterType.PRINTSERVER), + ]); + flush(); + assertEquals(2, entryManager.printServerPrinters.length); + + // This will attempt to add duplicate print server printers. + // Matching printerId's are considered duplicates. + await addPrintServer('serverAddress', PrintServerResult.NO_ERRORS); + flush(); + + verifyToastMessage('printServerFoundManyPrinters', /*numPrinters=*/ 2); + // Assert that adding the same print server results in no new printers + // added to the entry manager. + assertEquals(2, entryManager.printServerPrinters.length); + const nearbyPrintersElement = + page.shadowRoot!.querySelector('settings-cups-nearby-printers'); + assertTrue(!!nearbyPrintersElement); + assertEquals(2, nearbyPrintersElement.nearbyPrinters.length); + }); + + test('HandleDuplicateSavedPrinters', async () => { + // Initialize the return result from adding a print server. + cupsPrintersBrowserProxy.printServerPrinters = { + printerList: [ + createCupsPrinterInfo('nameA', 'serverAddress', 'idA'), + createCupsPrinterInfo('nameB', 'serverAddress', 'idB'), + ], + }; + + await flushTasks(); + // Simulate that a print server was queried previously. + setEntryManagerPrinters( + /*savedPrinters=*/[], /*nearbyPrinters=*/[], + /*discoveredPrinters=*/[], [ + createPrinterListEntry( + 'nameA', 'serverAddress', 'idA', PrinterType.PRINTSERVER), + createPrinterListEntry( + 'nameB', 'serverAddress', 'idB', PrinterType.PRINTSERVER), + ]); + flush(); + assertEquals(2, entryManager.printServerPrinters.length); + + // Simulate adding a saved printer. + entryManager.setSavedPrintersList([createPrinterListEntry( + 'nameA', 'serverAddress', 'idA', PrinterType.SAVED)]); + flush(); + + // Simulate the underlying model changes. Nearby printers are also + // updated after changes to saved printers. + webUIListenerCallback( + 'on-nearby-printers-changed', /*automaticPrinter=*/[], + /*discoveredPrinters=*/[]); + await flushTasks(); + + // Verify that we now only have 1 printer in print server printers + // list. + assertEquals(1, entryManager.printServerPrinters.length); + const nearbyPrintersElement = + page.shadowRoot!.querySelector('settings-cups-nearby-printers'); + assertTrue(!!nearbyPrintersElement); + assertEquals(1, nearbyPrintersElement.nearbyPrinters.length); + // Verify we correctly removed the duplicate printer, 'idA', since + // it exists in the saved printer list. Expect only 'idB' in + // the print server printers list. + assertEquals( + 'idB', entryManager.printServerPrinters[0]!.printerInfo.printerId); + }); + + test('AddPrintServerAddressError', async () => { + cupsPrintersBrowserProxy.printServerPrinters = {printerList: []}; + await addPrintServer('serverAddress', PrintServerResult.INCORRECT_URL); + flush(); + const printServerDialog = getPrintServerDialog(page); + // Assert that the dialog did not close on errors. + assertTrue(!!printServerDialog); + const printServerAddressInput = + printServerDialog.shadowRoot!.querySelector<CrInputElement>( + '#printServerAddressInput'); + assertTrue(!!printServerAddressInput); + // Assert that the address input field is invalid. + assertTrue(printServerAddressInput.invalid); + }); + + test('AddPrintServerConnectionError', async () => { + cupsPrintersBrowserProxy.printServerPrinters = {printerList: []}; + await addPrintServer('serverAddress', PrintServerResult.CONNECTION_ERROR); + flush(); + verifyErrorMessage('printServerConnectionError'); + }); + + test('AddPrintServerReachableServerButIppResponseError', async () => { + cupsPrintersBrowserProxy.printServerPrinters = {printerList: []}; + await addPrintServer( + 'serverAddress', PrintServerResult.CANNOT_PARSE_IPP_RESPONSE); + flush(); + verifyErrorMessage('printServerConfigurationErrorMessage'); + }); + + test('AddPrintServerReachableServerButHttpResponseError', async () => { + cupsPrintersBrowserProxy.printServerPrinters = {printerList: []}; + await addPrintServer('serverAddress', PrintServerResult.HTTP_ERROR); + flush(); + verifyErrorMessage('printServerConfigurationErrorMessage'); + }); +});
diff --git a/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_dialog_test.ts b/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_dialog_test.ts new file mode 100644 index 0000000..3e7cea12 --- /dev/null +++ b/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_dialog_test.ts
@@ -0,0 +1,1238 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {AddPrinterManuallyDialogElement, AddPrinterManufacturerModelDialogElement, CupsPrintersBrowserProxyImpl, PrinterSetupResult, SettingsCupsAddPrinterDialogElement, SettingsCupsEditPrinterDialogElement, SettingsCupsPrintersElement} from 'chrome://os-settings/lazy_load.js'; +import {CrInputElement, CrSearchableDropDownElement, Router, routes} from 'chrome://os-settings/os_settings.js'; +import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js'; +import {NetworkStateProperties} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js'; +import {ConnectionStateType, NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js'; +import {keyEventOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js'; +import {DomIf, flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {assertEquals, assertFalse, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js'; +import {flushTasks} from 'chrome://webui-test/polymer_test_util.js'; + +import {createCupsPrinterInfo} from './cups_printer_test_utils.js'; +import {TestCupsPrintersBrowserProxy} from './test_cups_printers_browser_proxy.js'; + +/* + * Helper function that waits for |getEulaUrl| to get called and then verifies + * its arguments. + */ +async function verifyGetEulaUrlWasCalled( + cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy, + expectedManufacturer: string, expectedModel: string): Promise<void> { + const args = await cupsPrintersBrowserProxy.whenCalled('getEulaUrl'); + assertEquals(expectedManufacturer, args[0]); // ppdManufacturer + assertEquals(expectedModel, args[1]); // ppdModel +} + +/* + * Helper function that resets the resolver for |getEulaUrl| and sets the new + * EULA URL. + */ +function resetGetEulaUrl( + cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy, + eulaUrl: string): void { + cupsPrintersBrowserProxy.resetResolver('getEulaUrl'); + cupsPrintersBrowserProxy.setEulaUrl(eulaUrl); +} + +suite('CupsAddPrinterDialogTests', () => { + let page: SettingsCupsPrintersElement; + let dialog: SettingsCupsAddPrinterDialogElement; + let cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy; + + function fillAddManuallyDialog(addDialog: AddPrinterManuallyDialogElement): + void { + const name = addDialog.shadowRoot!.querySelector<HTMLInputElement>( + '#printerNameInput'); + const address = addDialog.shadowRoot!.querySelector<HTMLInputElement>( + '#printerAddressInput'); + + assertTrue(!!name); + name.value = 'Test printer'; + + assertTrue(!!address); + address.value = '127.0.0.1'; + + const addButton = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( + '#addPrinterButton'); + assertTrue(!!addButton); + assertFalse(addButton.disabled); + } + + function clickAddButton(dialog: AddPrinterManuallyDialogElement) { + assertTrue(!!dialog, 'Dialog is null for add'); + const addButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + assertTrue(!!addButton, 'Button is null'); + addButton.click(); + } + + function clickCancelButton(dialog: AddPrinterManufacturerModelDialogElement) { + assertTrue(!!dialog, 'Dialog is null for cancel'); + const cancelButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.cancel-button'); + assertTrue(!!cancelButton, 'Button is null'); + cancelButton.click(); + } + + function canAddPrinter( + dialog: AddPrinterManuallyDialogElement, name: string, address: string) { + dialog.newPrinter.printerName = name; + dialog.newPrinter.printerAddress = address; + return dialog['canAddPrinter_'](); + } + + function mockAddPrinterInputKeyboardPress(crInputId: string) { + // Start in add manual dialog. + const addDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addDialog); + + // Test that pressing Enter before all the fields are populated does not + // advance to the next dialog. + const input = addDialog.shadowRoot!.querySelector(crInputId); + assertTrue(!!input); + keyEventOn(input, 'keypress', /*keycode=*/ 13, [], 'Enter'); + flush(); + + assertNull(dialog.shadowRoot!.querySelector( + 'add-printer-manufacturer-model-dialog')); + assertFalse(dialog.get('showManufacturerDialog_')); + assertTrue(dialog.get('showManuallyAddDialog_')); + + // Add valid input into the dialog + fillAddManuallyDialog(addDialog); + + // Test that key press on random key while in input field is not accepted as + // as valid Enter press. + keyEventOn(input, 'keypress', /*keycode=*/ 16, [], 'Shift'); + flush(); + + assertNull(dialog.shadowRoot!.querySelector( + 'add-printer-manufacturer-model-dialog')); + assertFalse(dialog.get('showManufacturerDialog_')); + assertTrue(dialog.get('showManuallyAddDialog_')); + + // Now test Enter press with valid input. + keyEventOn(input, 'keypress', /*keycode=*/ 13, [], 'Enter'); + flush(); + } + + setup(() => { + cupsPrintersBrowserProxy = new TestCupsPrintersBrowserProxy(); + CupsPrintersBrowserProxyImpl.setInstanceForTesting( + cupsPrintersBrowserProxy); + + page = document.createElement('settings-cups-printers'); + // TODO(jimmyxgong): Remove this line when the feature flag is removed. + page.set('enableUpdatedUi_', false); + document.body.appendChild(page); + assertTrue(!!page); + const element = + page.shadowRoot!.querySelector('settings-cups-add-printer-dialog'); + assertTrue(!!element); + dialog = element; + + dialog.open(); + return flushTasks(); + }); + + teardown(() => { + cupsPrintersBrowserProxy.reset(); + page.remove(); + dialog.remove(); + }); + + test('ValidIPV4', () => { + const dialog = document.createElement('add-printer-manually-dialog'); + assertTrue(canAddPrinter(dialog, 'Test printer', '127.0.0.1')); + }); + + test('ValidIPV4WithPort', () => { + const dialog = document.createElement('add-printer-manually-dialog'); + assertTrue(canAddPrinter(dialog, 'Test printer', '127.0.0.1:1234')); + }); + + test('ValidIPV6', () => { + const dialog = document.createElement('add-printer-manually-dialog'); + + // Test the full ipv6 address scheme. + assertTrue(canAddPrinter(dialog, 'Test printer', '1:2:a3:ff4:5:6:7:8')); + + // Test the shorthand prefix scheme. + assertTrue(canAddPrinter(dialog, 'Test printer', '::255')); + + // Test the shorthand suffix scheme. + assertTrue(canAddPrinter(dialog, 'Test printer', '1::')); + }); + + test('ValidIPV6WithPort', () => { + const dialog = document.createElement('add-printer-manually-dialog'); + + assertTrue(canAddPrinter(dialog, 'Test printer', '[1:2:aa2:4]:12')); + assertTrue(canAddPrinter(dialog, 'Test printer', '[::255]:54')); + assertTrue(canAddPrinter(dialog, 'Test printer', '[1::]:7899')); + }); + + test('InvalidIPV6', () => { + const dialog = document.createElement('add-printer-manually-dialog'); + + assertFalse(canAddPrinter(dialog, 'Test printer', '1:2:3:4:5:6:7:8:9')); + assertFalse(canAddPrinter(dialog, 'Test printer', '1:2:3:aa:a1245:2')); + assertFalse(canAddPrinter(dialog, 'Test printer', '1:2:3:za:2')); + assertFalse(canAddPrinter(dialog, 'Test printer', '1:::22')); + assertFalse(canAddPrinter(dialog, 'Test printer', '1::2::3')); + }); + + test('ValidHostname', () => { + const dialog = document.createElement('add-printer-manually-dialog'); + + assertTrue(canAddPrinter(dialog, 'Test printer', 'hello-world.com')); + assertTrue(canAddPrinter(dialog, 'Test printer', 'hello.world.com:12345')); + }); + + test('InvalidHostname', () => { + const dialog = document.createElement('add-printer-manually-dialog'); + + assertFalse(canAddPrinter(dialog, 'Test printer', 'helloworld!123.com')); + assertFalse(canAddPrinter(dialog, 'Test printer', 'helloworld123-.com')); + assertFalse(canAddPrinter(dialog, 'Test printer', '-helloworld123.com')); + }); + + /** + * Test that clicking on Add opens the model select page. + */ + test('ValidAddOpensModelSelection', async () => { + // Starts in add manual dialog. + const addDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addDialog); + flush(); + fillAddManuallyDialog(addDialog); + + const button = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( + '.action-button'); + assertTrue(!!button); + button.click(); + flush(); + + // Upon rejection, show model. + await cupsPrintersBrowserProxy.whenCalled( + 'getCupsPrinterManufacturersList'); + await flushTasks(); + // Showing model selection. + assertTrue(!!dialog.shadowRoot!.querySelector( + 'add-printer-manufacturer-model-dialog')); + + assertTrue(dialog.get('showManufacturerDialog_')); + assertFalse(dialog.get('showManuallyAddDialog_')); + }); + + /** + * Test that when getPrinterInfo fails for a generic reason, the general error + * message is shown. + */ + test('GetPrinterInfoFailsGeneralError', async () => { + // Starts in add manual dialog. + const addDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addDialog); + flush(); + + fillAddManuallyDialog(addDialog); + + // Make the getPrinterInfo fail for a generic error. + cupsPrintersBrowserProxy.setGetPrinterInfoResult( + PrinterSetupResult.FATAL_ERROR); + + // Attempt to add the printer. + const button = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( + '.action-button'); + assertTrue(!!button); + button.click(); + flush(); + + // Upon rejection, show model. + await cupsPrintersBrowserProxy.whenCalled('getPrinterInfo'); + // The general error should be showing. + assertTrue(!!addDialog.get('errorText_')); + const generalErrorElement = + addDialog.shadowRoot!.querySelector('printer-dialog-error'); + assertTrue(!!generalErrorElement); + const errorContainer = + generalErrorElement.shadowRoot!.querySelector<HTMLElement>( + '#error-container'); + assertTrue(!!errorContainer); + assertFalse(errorContainer.hidden); + }); + + /** + * Test that when getPrinterInfo fails for an unreachable printer, the + printer + * address field is marked as invalid. + */ + test('GetPrinterInfoFailsUnreachableError', async () => { + // Starts in add manual dialog. + const addDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addDialog); + flush(); + + fillAddManuallyDialog(addDialog); + + // Make the getPrinterInfo fail for an unreachable printer. + cupsPrintersBrowserProxy.setGetPrinterInfoResult( + PrinterSetupResult.PRINTER_UNREACHABLE); + + // Attempt to add the printer. + const button = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( + '.action-button'); + assertTrue(!!button); + button.click(); + flush(); + + // Upon rejection, show model. + await cupsPrintersBrowserProxy.whenCalled('getPrinterInfo'); + + // The printer address input should be marked as invalid. + const printerAddressInput = + addDialog.shadowRoot!.querySelector<CrInputElement>( + '#printerAddressInput'); + assertTrue(!!printerAddressInput); + assertTrue(printerAddressInput.invalid); + }); + + /** + * Test that getModels isn't called with a blank query. + */ + test('NoBlankQueries', async () => { + // Starts in add manual dialog. + const addDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addDialog); + flush(); + fillAddManuallyDialog(addDialog); + + cupsPrintersBrowserProxy.manufacturers = { + success: false, + manufacturers: ['ManufacturerA', 'ManufacturerB', 'Chromites'], + }; + const button = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( + '.action-button'); + assertTrue(!!button); + button.click(); + flush(); + + await cupsPrintersBrowserProxy.whenCalled( + 'getCupsPrinterManufacturersList'); + + // Verify that getCupsPrinterModelList is not called. + assertEquals( + 0, cupsPrintersBrowserProxy.getCallCount('getCupsPrinterModelsList')); + + const modelDialog = dialog.shadowRoot!.querySelector( + 'add-printer-manufacturer-model-dialog'); + + // Manufacturer dialog has been rendered and the model list was not + // requested. We're done. + assertTrue(!!modelDialog); + }); + + /** + * Test that dialog cancellation is logged from the manufacturer screen for + * IPP printers. + */ + test('LogDialogCancelledIpp', async () => { + const makeAndModel = 'Printer Make And Model'; + // Start on add manual dialog. + dialog.dispatchEvent(new CustomEvent('open-manually-add-printer-dialog')); + flush(); + + // Populate the printer object. + dialog.newPrinter = { + isManaged: false, + ppdManufacturer: '', + ppdModel: '', + printerAddress: '192.168.1.13', + printerDescription: '', + printerId: '', + printerMakeAndModel: '', + printerName: 'Test Printer', + printerPPDPath: '', + printerPpdReference: { + userSuppliedPpdUrl: '', + effectiveMakeAndModel: '', + autoconf: false, + }, + printerProtocol: 'ipps', + printerQueue: 'moreinfohere', + printServerUri: '', + }; + + // Seed the getPrinterInfo response. We detect the make and model but it is + // not an autoconf printer. + cupsPrintersBrowserProxy.printerInfo = { + autoconf: false, + makeAndModel, + ppdRefUserSuppliedPpdUrl: 'ppd url', + ppdRefEffectiveMakeAndModel: 'Effective Make And Model', + ppdReferenceResolved: false, + }; + + // Press the add button to advance dialog. + const addDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addDialog); + flush(); + clickAddButton(addDialog); + + // Click cancel on the manufacturer dialog when it shows up then verify + // cancel was called with the appropriate parameters. + await cupsPrintersBrowserProxy.whenCalled( + 'getCupsPrinterManufacturersList'); + flush(); + // Cancel setup with the cancel button. + const modelDialog = dialog.shadowRoot!.querySelector( + 'add-printer-manufacturer-model-dialog'); + assertTrue(!!modelDialog); + clickCancelButton(modelDialog); + const printer = + await cupsPrintersBrowserProxy.whenCalled('cancelPrinterSetUp'); + assertTrue(!!printer, 'New printer is null'); + assertEquals(makeAndModel, printer.printerMakeAndModel); + }); + + /** + * Test that we are checking if a printer model has an EULA upon a model + * change. + */ + test('getEulaUrlGetsCalledOnModelChange', async () => { + // Start in add manual dialog. + const addDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addDialog); + flush(); + fillAddManuallyDialog(addDialog); + + const button = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( + '.action-button'); + assertTrue(!!button); + button.click(); + flush(); + + const expectedEulaLink = 'chrome://os-credits/#google'; + const expectedManufacturer = 'Google'; + const expectedModel = 'printer'; + const expectedModel2 = 'newPrinter'; + const expectedModel3 = 'newPrinter2'; + + await cupsPrintersBrowserProxy.whenCalled( + 'getCupsPrinterManufacturersList'); + const modelDialog = dialog.shadowRoot!.querySelector( + 'add-printer-manufacturer-model-dialog'); + assertTrue(!!modelDialog); + + const urlElement = + modelDialog.shadowRoot!.querySelector<HTMLElement>('#eulaUrl'); + assertTrue(!!urlElement); + // Check that the EULA text is not shown. + assertTrue(urlElement.hidden); + + cupsPrintersBrowserProxy.setEulaUrl(expectedEulaLink); + + const manufacturerDropdown = + modelDialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( + '#manufacturerDropdown'); + assertTrue(!!manufacturerDropdown); + manufacturerDropdown.value = expectedManufacturer; + const modelDropdown = + modelDialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( + '#modelDropdown'); + assertTrue(!!modelDropdown); + modelDropdown.value = expectedModel; + await verifyGetEulaUrlWasCalled( + cupsPrintersBrowserProxy, expectedManufacturer, expectedModel); + + // Check that the EULA text is shown. + assertFalse(urlElement.hidden); + let anchor = urlElement.querySelector('a'); + assertTrue(!!anchor); + assertEquals(expectedEulaLink, anchor.href); + + resetGetEulaUrl(cupsPrintersBrowserProxy, '' /* eulaUrl */); + + // Change ppdModel and expect |getEulaUrl| to be called again. + modelDropdown.value = expectedModel2; + await verifyGetEulaUrlWasCalled( + cupsPrintersBrowserProxy, expectedManufacturer, expectedModel2); + + // Check that the EULA text is hidden. + assertTrue(urlElement.hidden); + + resetGetEulaUrl(cupsPrintersBrowserProxy, expectedEulaLink); + + // Change ppdModel and expect |getEulaUrl| to be called again. + modelDropdown.value = expectedModel3; + await verifyGetEulaUrlWasCalled( + cupsPrintersBrowserProxy, expectedManufacturer, expectedModel3); + assertFalse(urlElement.hidden); + anchor = urlElement.querySelector('a'); + assertTrue(!!anchor); + assertEquals(expectedEulaLink, anchor.href); + }); + + /** + * Test that the add button of the manufacturer dialog is disabled after + * clicking it. + */ + test('AddButtonDisabledAfterClicking', async () => { + // From the add manually dialog, click the add button to advance to the + // manufacturer dialog. + const addDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addDialog); + flush(); + fillAddManuallyDialog(addDialog); + clickAddButton(addDialog); + flush(); + + // Click the add button on the manufacturer dialog and then verify it is + // disabled. + await cupsPrintersBrowserProxy.whenCalled( + 'getCupsPrinterManufacturersList'); + const manufacturerDialog = dialog.shadowRoot!.querySelector( + 'add-printer-manufacturer-model-dialog'); + assertTrue(!!manufacturerDialog); + + // Populate the manufacturer and model fields to enable the add + // button. + const manufacturerDropdown = + manufacturerDialog.shadowRoot! + .querySelector<CrSearchableDropDownElement>( + '#manufacturerDropdown'); + assertTrue(!!manufacturerDropdown); + manufacturerDropdown.value = 'make'; + const modelDropdown = + manufacturerDialog.shadowRoot! + .querySelector<CrSearchableDropDownElement>('#modelDropdown'); + assertTrue(!!modelDropdown); + modelDropdown.value = 'model'; + + const addButton = + manufacturerDialog.shadowRoot!.querySelector<HTMLButtonElement>( + '#addPrinterButton'); + assertTrue(!!addButton); + assertFalse(addButton.disabled); + addButton.click(); + assertTrue(addButton.disabled); + }); + + /** + * The following tests check that clicking Enter button on the keyboard + from + * each input text field on the add-printer-manually-dialog will advance to + * the next dialog. + */ + test('PressEnterInPrinterNameInput', async () => { + mockAddPrinterInputKeyboardPress('#printerNameInput'); + + // Upon rejection, show model. + await cupsPrintersBrowserProxy.whenCalled( + 'getCupsPrinterManufacturersList'); + await flushTasks(); + // Showing model selection. + assertTrue(!!dialog.shadowRoot!.querySelector( + 'add-printer-manufacturer-model-dialog')); + assertTrue(dialog.get('showManufacturerDialog_')); + assertFalse(dialog.get('showManuallyAddDialog_')); + }); + + test('PressEnterInPrinterAddressInput', async () => { + mockAddPrinterInputKeyboardPress('#printerAddressInput'); + + // Upon rejection, show model. + await cupsPrintersBrowserProxy.whenCalled( + 'getCupsPrinterManufacturersList'); + await flushTasks(); + // Showing model selection. + assertNull( + dialog.shadowRoot!.querySelector('add-printer-configuring-dialog')); + assertTrue(dialog.get('showManufacturerDialog_')); + assertFalse(dialog.get('showManuallyAddDialog_')); + }); + + test('PressEnterInPrinterQueueInput', async () => { + mockAddPrinterInputKeyboardPress('#printerQueueInput'); + + // Upon rejection, show model. + await cupsPrintersBrowserProxy.whenCalled( + 'getCupsPrinterManufacturersList'); + await flushTasks(); + // Showing model selection. + assertTrue(!!dialog.shadowRoot!.querySelector( + 'add-printer-manufacturer-model-dialog')); + assertTrue(dialog.get('showManufacturerDialog_')); + assertFalse(dialog.get('showManuallyAddDialog_')); + }); + + /** + * Test that the add button of the manufacturer dialog is disabled when the + * manufacturer or model dropdown has an incorrect value. + */ + test('AddButtonDisabledAfterClicking', async () => { + // From the add manually dialog, click the add button to advance to the + // manufacturer dialog. + const addDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addDialog); + flush(); + fillAddManuallyDialog(addDialog); + clickAddButton(addDialog); + flush(); + + await cupsPrintersBrowserProxy.whenCalled( + 'getCupsPrinterManufacturersList'); + const manufacturerDialog = dialog.shadowRoot!.querySelector( + 'add-printer-manufacturer-model-dialog'); + assertTrue(!!manufacturerDialog); + + const manufacturerDropdown = + manufacturerDialog.shadowRoot! + .querySelector<CrSearchableDropDownElement>( + '#manufacturerDropdown'); + const modelDropdown = + manufacturerDialog.shadowRoot! + .querySelector<CrSearchableDropDownElement>('#modelDropdown'); + const addButton = + manufacturerDialog.shadowRoot!.querySelector<HTMLButtonElement>( + '#addPrinterButton'); + + assertTrue(!!manufacturerDropdown); + assertTrue(!!modelDropdown); + assertTrue(!!addButton); + // Set the starting values for manufacturer and model dropdown. + manufacturerDropdown.value = 'make'; + modelDropdown.value = 'model'; + assertFalse(addButton.disabled); + + // Mimic typing in random input. Make sure the Add button becomes + // disabled. + let searchElement = + manufacturerDropdown.shadowRoot!.querySelector<HTMLInputElement>( + '#search'); + assertTrue(!!searchElement); + searchElement.value = 'hlrRkJQkNsh'; + searchElement.dispatchEvent(new CustomEvent('input')); + assertTrue(addButton.disabled); + + // Then mimic typing in the original value to re-enable the Add + // button. + searchElement = + manufacturerDropdown.shadowRoot!.querySelector<HTMLInputElement>( + '#search'); + assertTrue(!!searchElement); + searchElement.value = 'make'; + searchElement.dispatchEvent(new CustomEvent('input')); + assertFalse(addButton.disabled); + + // Mimic typing in random input. Make sure the Add button becomes + // disabled. + searchElement = + modelDropdown.shadowRoot!.querySelector<HTMLInputElement>('#search'); + assertTrue(!!searchElement); + searchElement.value = 'hlrRkJQkNsh'; + searchElement.dispatchEvent(new CustomEvent('input')); + assertTrue(addButton.disabled); + + // Then mimic typing in the original value to re-enable the Add + // button. + searchElement = + modelDropdown.shadowRoot!.querySelector<HTMLInputElement>('#search'); + assertTrue(!!searchElement); + searchElement.value = 'model'; + searchElement.dispatchEvent(new CustomEvent('input')); + assertFalse(addButton.disabled); + }); + + test('Queue input is hidden when protocol is App Socket', () => { + const addDialog = + dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); + assertTrue(!!addDialog); + let printerQueueInput = + addDialog.shadowRoot!.querySelector('#printerQueueInput'); + assertTrue(!!printerQueueInput); + const select = addDialog.shadowRoot!.querySelector('select'); + assertTrue(!!select); + + select.value = 'socket'; + select.dispatchEvent(new CustomEvent('change', {'bubbles': true})); + flush(); + + printerQueueInput = + addDialog.shadowRoot!.querySelector('#printerQueueInput'); + assertNull(printerQueueInput); + + select.value = 'http'; + select.dispatchEvent(new CustomEvent('change', {'bubbles': true})); + flush(); + + printerQueueInput = + addDialog.shadowRoot!.querySelector('#printerQueueInput'); + assertTrue(!!printerQueueInput); + }); +}); + +suite('EditPrinterDialog', () => { + let page: SettingsCupsPrintersElement; + let dialog: SettingsCupsEditPrinterDialogElement; + let cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy; + let wifi1: NetworkStateProperties; + + setup(async () => { + cupsPrintersBrowserProxy = new TestCupsPrintersBrowserProxy(); + + CupsPrintersBrowserProxyImpl.setInstanceForTesting( + cupsPrintersBrowserProxy); + + // Simulate internet connection. + wifi1 = OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi1'); + wifi1.connectionState = ConnectionStateType.kOnline; + + Router.getInstance().navigateTo(routes.CUPS_PRINTERS); + + page = document.createElement('settings-cups-printers'); + document.body.appendChild(page); + assertTrue(!!page); + page.onActiveNetworksChanged([wifi1]); + await flushTasks(); + }); + + teardown(() => { + cupsPrintersBrowserProxy.reset(); + page.remove(); + dialog.remove(); + }); + + function clickSaveButton(dialog: SettingsCupsEditPrinterDialogElement) { + assertTrue(!!dialog, 'Dialog is null for save'); + const saveButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + dialog.set('printerInfoChanged_', true); + assertTrue(!!saveButton, 'Button is null'); + assertFalse(saveButton.disabled); + saveButton.click(); + } + + function clickCancelButton(dialog: SettingsCupsEditPrinterDialogElement) { + assertTrue(!!dialog, 'Dialog is null for cancel'); + const cancelButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.cancel-button'); + assertTrue(!!cancelButton, 'Button is null'); + cancelButton.click(); + } + + /** + * Initializes a printer and sets that printer as the printer to be edited in + * the edit dialog. Opens the edit dialog. + */ + async function initializeAndOpenEditDialog( + name: string, address: string, id: string, autoconf: boolean, + manufacturer: string, model: string, protocol: string, + serverAddress: string): Promise<void> { + page.activePrinter = createCupsPrinterInfo(name, address, id); + page.activePrinter.printerPpdReference.autoconf = autoconf; + page.activePrinter.printerProtocol = protocol; + page.activePrinter.printServerUri = serverAddress; + cupsPrintersBrowserProxy.printerPpdMakeModel = { + ppdManufacturer: manufacturer, + ppdModel: model, + }; + // Trigger the edit dialog to open. + page.dispatchEvent(new CustomEvent('edit-cups-printer-details')); + flush(); + const element = + page.shadowRoot!.querySelector('settings-cups-edit-printer-dialog'); + assertTrue(!!element); + dialog = element; + // This proxy function gets called whenever the edit dialog is initialized. + await cupsPrintersBrowserProxy.whenCalled('getCupsPrinterModelsList'); + } + + /** + * Test that USB printers can be edited. + */ + test('USBPrinterCanBeEdited', async () => { + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'usb', /*serverAddress=*/ ''); + // Assert that the protocol is USB. + const selectElement = + dialog.shadowRoot!.querySelector<HTMLSelectElement>('.md-select'); + assertTrue(!!selectElement); + assertEquals('usb', selectElement.value); + + // Edit the printer name. + const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( + '.printer-name-input'); + assertTrue(!!nameField); + nameField.value = 'edited printer'; + nameField.dispatchEvent(new CustomEvent('input')); + + // Assert that the "Save" button is enabled. + const saveButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + assertTrue(!!saveButton); + assertFalse(saveButton.disabled); + }); + + /** + * Test that the save button is disabled when the printer address or name is + * invalid. + */ + test('EditPrinter', async () => { + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + const printerName = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerName'); + const printerAddress = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); + assertTrue(!!printerName); + assertTrue(!!printerAddress); + + const saveButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + assertTrue(!!saveButton); + assertTrue(saveButton.disabled); + + // Change printer name to something valid. + printerName.value = 'new printer name'; + printerName.dispatchEvent(new CustomEvent('input')); + assertFalse(saveButton.disabled); + + // Change printer address to something invalid. + printerAddress.value = 'abcdef:'; + assertTrue(saveButton.disabled); + + // Change back to something valid. + printerAddress.value = 'abcdef:1234'; + assertFalse(saveButton.disabled); + + // Change printer name to empty. + printerName.value = ''; + assertTrue(saveButton.disabled); + }); + + /** + * Test that closing the dialog does not persist the edits. + */ + test('CloseEditDialogDoesNotModifyActivePrinter', async () => { + const expectedName = 'Test Printer'; + const expectedAddress = '1.1.1.1'; + const expectedId = 'ID1'; + const expectedProtocol = 'ipp'; + await initializeAndOpenEditDialog( + /*name=*/ expectedName, /*address=*/ expectedAddress, + /*id=*/ expectedId, /*autoconf=*/ false, + /*manufacturer=*/ 'make', /*model=*/ 'model', + /*protocol=*/ expectedProtocol, /*serverAddress=*/ ''); + const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( + '.printer-name-input'); + assertTrue(!!nameField); + nameField.value = 'edited printer name'; + + const addressField = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); + assertTrue(!!addressField); + addressField.value = '9.9.9.9'; + + const protocolField = + dialog.shadowRoot!.querySelector<HTMLSelectElement>('.md-select'); + assertTrue(!!protocolField); + protocolField.value = 'http'; + + clickCancelButton(dialog); + + // Assert that activePrinter properties were not changed. + assertEquals(expectedName, dialog.activePrinter.printerName); + assertEquals(expectedAddress, dialog.activePrinter.printerAddress); + assertEquals(expectedProtocol, dialog.activePrinter.printerProtocol); + }); + + /** + * Test that editing the name field results in properly saving the new name. + */ + test('TestEditNameAndSave', async () => { + const expectedName = 'editedName'; + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( + '.printer-name-input'); + assertTrue(!!nameField); + nameField.value = expectedName; + + flush(); + clickSaveButton(dialog); + await cupsPrintersBrowserProxy.whenCalled('updateCupsPrinter'); + assertEquals(expectedName, dialog.activePrinter.printerName); + }); + + /** + * Test that editing various fields results in properly saving the new + * changes. + */ + test('TestEditFieldsAndSave', async () => { + const expectedAddress = '9.9.9.9'; + const expectedQueue = 'editedQueue'; + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + + // Editing more than just the printer name requires reconfiguring the + // printer. + const addressField = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); + assertTrue(!!addressField); + addressField.value = expectedAddress; + + const queueField = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerQueue'); + assertTrue(!!queueField); + queueField.value = expectedQueue; + + clickSaveButton(dialog); + await cupsPrintersBrowserProxy.whenCalled('reconfigureCupsPrinter'); + assertEquals(expectedAddress, dialog.activePrinter.printerAddress); + assertEquals(expectedQueue, dialog.activePrinter.printerQueue); + }); + + /** + * Test that editing an autoconf printer saves correctly. + */ + test('TestEditAutoConfFieldsAndSave', async () => { + const expectedAddress = '9.9.9.9'; + const expectedQueue = 'editedQueue'; + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ true, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + // Editing more than just the printer name requires reconfiguring the + // printer. + const addressField = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); + assertTrue(!!addressField); + addressField.value = expectedAddress; + + const queueField = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerQueue'); + assertTrue(!!queueField); + queueField.value = expectedQueue; + + clickSaveButton(dialog); + await cupsPrintersBrowserProxy.whenCalled('reconfigureCupsPrinter'); + assertEquals(expectedAddress, dialog.activePrinter.printerAddress); + assertEquals(expectedQueue, dialog.activePrinter.printerQueue); + }); + + /** + * Test that non-autoconf printers can select the make and model dropdowns. + */ + test('TestNonAutoConfPrintersCanSelectManufactureAndModel', async () => { + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + // Assert that the manufacturer and model drop-downs are shown. + const element = + dialog.shadowRoot!.querySelector<HTMLElement>('#makeAndModelSection'); + assertTrue(!!element); + assertFalse(element.hidden); + }); + + /** + * Test that autoconf printers cannot select their make/model + */ + test('TestAutoConfPrintersCannotSelectManufactureAndModel', async () => { + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ true, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + // Assert that the manufacturer and model drop-downs are hidden. + const element = + dialog.shadowRoot!.querySelector<DomIf>('#makeAndModelSection'); + assertTrue(!!element); + assertTrue(!element.if); + }); + + /** + * Test that changing the name enables the save button. + */ + test('TestChangingNameEnablesSaveButton', async () => { + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + const saveButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + assertTrue(!!saveButton); + assertTrue(saveButton.disabled); + + const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( + '.printer-name-input'); + assertTrue(!!nameField); + nameField.value = 'edited printer'; + nameField.dispatchEvent(new CustomEvent('input')); + + assertFalse(saveButton.disabled); + }); + + /** + * Test that changing the address enables the save button. + */ + test('TestChangingAddressEnablesSaveButton', async () => { + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + const saveButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + assertTrue(!!saveButton); + assertTrue(saveButton.disabled); + + const addressField = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); + assertTrue(!!addressField); + addressField.value = 'newAddress:789'; + addressField.dispatchEvent(new CustomEvent('input')); + + assertFalse(saveButton.disabled); + }); + + /** + * Test that changing the queue enables the save button. + */ + test('TestChangingQueueEnablesSaveButton', async () => { + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + const saveButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + assertTrue(!!saveButton); + assertTrue(saveButton.disabled); + + const queueField = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerQueue'); + assertTrue(!!queueField); + queueField.value = 'newqueueinfo'; + queueField.dispatchEvent(new CustomEvent('input')); + + assertFalse(saveButton.disabled); + }); + + /** + * Test that changing the protocol enables the save button. + */ + test('TestChangingProtocolEnablesSaveButton', async () => { + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + const saveButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + assertTrue(!!saveButton); + assertTrue(saveButton.disabled); + + const dropDown = + dialog.shadowRoot!.querySelector<HTMLSelectElement>('.md-select'); + assertTrue(!!dropDown); + dropDown.value = 'http'; + dropDown.dispatchEvent(new CustomEvent('change', {'bubbles': true})); + flush(); + assertFalse(saveButton.disabled); + }); + + /** + * Test that changing the model enables the save button. + */ + test('TestChangingModelEnablesSaveButton', async () => { + cupsPrintersBrowserProxy.manufacturers = { + success: true, + manufacturers: ['HP'], + }; + cupsPrintersBrowserProxy.models = {success: true, models: ['HP 910']}; + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + const saveButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + assertTrue(!!saveButton); + assertTrue(saveButton.disabled); + + const makeDropDown = + dialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( + '#printerPPDManufacturer'); + assertTrue(!!makeDropDown); + makeDropDown.value = 'HP'; + makeDropDown.dispatchEvent(new CustomEvent('change', {'bubbles': true})); + + await cupsPrintersBrowserProxy.whenCalled('getCupsPrinterModelsList'); + // Saving is disabled until a model is selected. + assertTrue(saveButton.disabled); + + const modelDropDown = + dialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( + '#printerPPDModel'); + assertTrue(!!modelDropDown); + modelDropDown.value = 'HP 910'; + modelDropDown.dispatchEvent(new CustomEvent('change', {'bubbles': true})); + + flush(); + assertFalse(saveButton.disabled); + }); + + /** + * Test that we are checking if a printer model has an EULA upon a model + * change. + */ + test('getEulaUrlGetsCalledOnModelChange', async () => { + const eulaLink = 'google.com'; + const expectedManufacturer = 'Google'; + const expectedModel = 'model'; + const expectedModel2 = 'newModel'; + const expectedModel3 = 'newModel2'; + + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + const urlElement = + dialog.shadowRoot!.querySelector<HTMLElement>('#eulaUrl'); + assertTrue(!!urlElement); + // Check that the EULA text is hidden. + assertTrue(urlElement.hidden); + + // 'getEulaUrl' is called as part of the initialization of the dialog, + // so we have to reset the resolver before the next call. + resetGetEulaUrl(cupsPrintersBrowserProxy, eulaLink); + + const makeDropDown = + dialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( + '#printerPPDManufacturer'); + assertTrue(!!makeDropDown); + makeDropDown.value = expectedManufacturer; + const modelDropdown = + dialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( + '#printerPPDModel'); + assertTrue(!!modelDropdown); + modelDropdown.value = expectedModel; + + await verifyGetEulaUrlWasCalled( + cupsPrintersBrowserProxy, expectedManufacturer, expectedModel); + + // Check that the EULA text is shown. + assertFalse(urlElement.hidden); + + resetGetEulaUrl(cupsPrintersBrowserProxy, /*eulaUrl=*/ ''); + + // Change ppdModel and expect |getEulaUrl| to be called again. + modelDropdown.value = expectedModel2; + await verifyGetEulaUrlWasCalled( + cupsPrintersBrowserProxy, expectedManufacturer, expectedModel2); + + // Check that the EULA text is hidden. + assertTrue(urlElement.hidden); + + resetGetEulaUrl(cupsPrintersBrowserProxy, eulaLink); + + // Change ppdModel and expect |getEulaUrl| to be called again. + modelDropdown.value = expectedModel3; + await verifyGetEulaUrlWasCalled( + cupsPrintersBrowserProxy, expectedManufacturer, expectedModel3); + + // Check that the EULA text is shown again. + assertFalse(urlElement.hidden); + }); + + /** + * Test that editing the name is still supported when offline. + */ + test('OfflineEdit', async () => { + // Simulate connecting to a network with no internet connection. + wifi1.connectionState = ConnectionStateType.kConnected; + page.onActiveNetworksChanged([wifi1]); + flush(); + const expectedName = 'editedName'; + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ false, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); + const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( + '.printer-name-input'); + assertTrue(!!nameField); + nameField.value = expectedName; + nameField.dispatchEvent(new CustomEvent('input')); + + flush(); + + const saveButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + assertTrue(!!saveButton); + assertFalse(saveButton.disabled); + + clickSaveButton(dialog); + await cupsPrintersBrowserProxy.whenCalled('updateCupsPrinter'); + assertEquals(expectedName, dialog.activePrinter.printerName); + }); + + test('PrintServerPrinterEdit', async () => { + const expectedName = 'edited name'; + await initializeAndOpenEditDialog( + /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', + /*autoconf=*/ true, /*manufacturer=*/ 'make', + /*model=*/ 'model', /*protocol=*/ 'ipp', + /*serverAddress=*/ 'ipp://192.168.1.1:631'); + + // Verify the only the name field is not disabled. + const printerAddress = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); + assertTrue(!!printerAddress); + assertTrue(printerAddress.disabled); + const selectElement = + dialog.shadowRoot!.querySelector<HTMLSelectElement>('.md-select'); + assertTrue(!!selectElement); + assertTrue(selectElement.disabled); + const printerQueue = + dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerQueue'); + assertTrue(!!printerQueue); + assertTrue(printerQueue.disabled); + + const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( + '.printer-name-input'); + assertTrue(!!nameField); + assertFalse(nameField.disabled); + + nameField.value = expectedName; + nameField.dispatchEvent(new CustomEvent('input')); + + flush(); + + const saveButton = + dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); + assertTrue(!!saveButton); + assertFalse(saveButton.disabled); + + clickSaveButton(dialog); + await cupsPrintersBrowserProxy.whenCalled('updateCupsPrinter'); + assertEquals(expectedName, dialog.activePrinter.printerName); + }); +});
diff --git a/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_landing_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_landing_page_test.ts index 217788d..3f6e3a6 100644 --- a/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_landing_page_test.ts +++ b/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_landing_page_test.ts
@@ -4,7 +4,7 @@ import 'chrome://os-settings/lazy_load.js'; -import {CupsPrinterInfo, CupsPrintersBrowserProxyImpl, PRINTER_STATUS_QUERY_SHORT_DELAY_RANGE_MS, PrinterListEntry, PrinterSettingsUserAction, PrinterStatusReason, PrinterStatusSeverity, PrinterType, SettingsCupsEditPrinterDialogElement, SettingsCupsEnterprisePrintersElement, SettingsCupsNearbyPrintersElement, SettingsCupsPrintersElement, SettingsCupsPrintersEntryElement, SettingsCupsSavedPrintersElement} from 'chrome://os-settings/lazy_load.js'; +import {CupsPrinterInfo, CupsPrintersBrowserProxyImpl, CupsPrintersEntryManager, PRINTER_STATUS_QUERY_SHORT_DELAY_RANGE_MS, PrinterListEntry, PrinterSettingsUserAction, PrinterStatusReason, PrinterStatusSeverity, PrinterType, SettingsCupsEditPrinterDialogElement, SettingsCupsEnterprisePrintersElement, SettingsCupsNearbyPrintersElement, SettingsCupsPrintersElement, SettingsCupsPrintersEntryElement, SettingsCupsSavedPrintersElement} from 'chrome://os-settings/lazy_load.js'; import {CrInputElement, CrSearchableDropDownElement, CrToastElement, Router, routes, settingMojom} from 'chrome://os-settings/os_settings.js'; import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js'; import {webUIListenerCallback} from 'chrome://resources/js/cr.js'; @@ -218,6 +218,7 @@ cupsPrintersBrowserProxy.reset(); page.remove(); savedPrintersElement.remove(); + CupsPrintersEntryManager.resetForTesting(); });
diff --git a/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_page_test.ts index a520e9da..7ac8cc01 100644 --- a/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_page_test.ts +++ b/chrome/test/data/webui/settings/chromeos/os_printing_page/cups_printer_page_test.ts
@@ -2,16 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {AddPrinterManuallyDialogElement, AddPrinterManufacturerModelDialogElement, AddPrintServerDialogElement, CupsPrinterInfo, CupsPrintersBrowserProxyImpl, CupsPrintersEntryManager, PrinterDialogErrorElement, PrinterListEntry, PrinterSettingsUserAction, PrinterSetupResult, PrinterType, PrintServerResult, SettingsCupsAddPrinterDialogElement, SettingsCupsEditPrinterDialogElement, SettingsCupsPrintersElement} from 'chrome://os-settings/lazy_load.js'; -import {CrInputElement, CrSearchableDropDownElement, CrToastElement, Router, routes} from 'chrome://os-settings/os_settings.js'; +import {CupsPrintersBrowserProxyImpl, PrinterSettingsUserAction, PrinterType, SettingsCupsPrintersElement} from 'chrome://os-settings/lazy_load.js'; +import {Router, routes} from 'chrome://os-settings/os_settings.js'; import {webUIListenerCallback} from 'chrome://resources/ash/common/cr.m.js'; -import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; -import {NetworkStateProperties} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js'; -import {ConnectionStateType, NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js'; -import {keyEventOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js'; -import {DomIf, flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; -import {assertEquals, assertFalse, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js'; +import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; import {isVisible} from 'chrome://webui-test/chromeos/test_util.js'; import {flushTasks} from 'chrome://webui-test/polymer_test_util.js'; @@ -20,29 +15,6 @@ import {createCupsPrinterInfo, createPrinterListEntry} from './cups_printer_test_utils.js'; import {TestCupsPrintersBrowserProxy} from './test_cups_printers_browser_proxy.js'; -/* - * Helper function that waits for |getEulaUrl| to get called and then verifies - * its arguments. - */ -async function verifyGetEulaUrlWasCalled( - cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy, - expectedManufacturer: string, expectedModel: string): Promise<void> { - const args = await cupsPrintersBrowserProxy.whenCalled('getEulaUrl'); - assertEquals(expectedManufacturer, args[0]); // ppdManufacturer - assertEquals(expectedModel, args[1]); // ppdModel -} - -/* - * Helper function that resets the resolver for |getEulaUrl| and sets the new - * EULA URL. - */ -function resetGetEulaUrl( - cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy, - eulaUrl: string): void { - cupsPrintersBrowserProxy.resetResolver('getEulaUrl'); - cupsPrintersBrowserProxy.setEulaUrl(eulaUrl); -} - suite('<settings-cups-printers>', () => { let page: SettingsCupsPrintersElement; let cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy; @@ -195,1472 +167,3 @@ PrinterSettingsUserAction.ADD_PRINTER_MANUALLY)); }); }); - -suite('CupsAddPrinterDialogTests', () => { - let page: SettingsCupsPrintersElement; - let dialog: SettingsCupsAddPrinterDialogElement; - let cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy; - - function fillAddManuallyDialog(addDialog: AddPrinterManuallyDialogElement): - void { - const name = addDialog.shadowRoot!.querySelector<HTMLInputElement>( - '#printerNameInput'); - const address = addDialog.shadowRoot!.querySelector<HTMLInputElement>( - '#printerAddressInput'); - - assertTrue(!!name); - name.value = 'Test printer'; - - assertTrue(!!address); - address.value = '127.0.0.1'; - - const addButton = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( - '#addPrinterButton'); - assertTrue(!!addButton); - assertFalse(addButton.disabled); - } - - function clickAddButton(dialog: AddPrinterManuallyDialogElement) { - assertTrue(!!dialog, 'Dialog is null for add'); - const addButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - assertTrue(!!addButton, 'Button is null'); - addButton.click(); - } - - function clickCancelButton(dialog: AddPrinterManufacturerModelDialogElement) { - assertTrue(!!dialog, 'Dialog is null for cancel'); - const cancelButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.cancel-button'); - assertTrue(!!cancelButton, 'Button is null'); - cancelButton.click(); - } - - function canAddPrinter( - dialog: AddPrinterManuallyDialogElement, name: string, address: string) { - dialog.newPrinter.printerName = name; - dialog.newPrinter.printerAddress = address; - return dialog['canAddPrinter_'](); - } - - function mockAddPrinterInputKeyboardPress(crInputId: string) { - // Start in add manual dialog. - const addDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addDialog); - - // Test that pressing Enter before all the fields are populated does not - // advance to the next dialog. - const input = addDialog.shadowRoot!.querySelector(crInputId); - assertTrue(!!input); - keyEventOn(input, 'keypress', /*keycode=*/ 13, [], 'Enter'); - flush(); - - assertNull(dialog.shadowRoot!.querySelector( - 'add-printer-manufacturer-model-dialog')); - assertFalse(dialog.get('showManufacturerDialog_')); - assertTrue(dialog.get('showManuallyAddDialog_')); - - // Add valid input into the dialog - fillAddManuallyDialog(addDialog); - - // Test that key press on random key while in input field is not accepted as - // as valid Enter press. - keyEventOn(input, 'keypress', /*keycode=*/ 16, [], 'Shift'); - flush(); - - assertNull(dialog.shadowRoot!.querySelector( - 'add-printer-manufacturer-model-dialog')); - assertFalse(dialog.get('showManufacturerDialog_')); - assertTrue(dialog.get('showManuallyAddDialog_')); - - // Now test Enter press with valid input. - keyEventOn(input, 'keypress', /*keycode=*/ 13, [], 'Enter'); - flush(); - } - - setup(() => { - cupsPrintersBrowserProxy = new TestCupsPrintersBrowserProxy(); - CupsPrintersBrowserProxyImpl.setInstanceForTesting( - cupsPrintersBrowserProxy); - - page = document.createElement('settings-cups-printers'); - // TODO(jimmyxgong): Remove this line when the feature flag is removed. - page.set('enableUpdatedUi_', false); - document.body.appendChild(page); - assertTrue(!!page); - const element = - page.shadowRoot!.querySelector('settings-cups-add-printer-dialog'); - assertTrue(!!element); - dialog = element; - - dialog.open(); - return flushTasks(); - }); - - teardown(() => { - cupsPrintersBrowserProxy.reset(); - page.remove(); - dialog.remove(); - }); - - test('ValidIPV4', () => { - const dialog = document.createElement('add-printer-manually-dialog'); - assertTrue(canAddPrinter(dialog, 'Test printer', '127.0.0.1')); - }); - - test('ValidIPV4WithPort', () => { - const dialog = document.createElement('add-printer-manually-dialog'); - assertTrue(canAddPrinter(dialog, 'Test printer', '127.0.0.1:1234')); - }); - - test('ValidIPV6', () => { - const dialog = document.createElement('add-printer-manually-dialog'); - - // Test the full ipv6 address scheme. - assertTrue(canAddPrinter(dialog, 'Test printer', '1:2:a3:ff4:5:6:7:8')); - - // Test the shorthand prefix scheme. - assertTrue(canAddPrinter(dialog, 'Test printer', '::255')); - - // Test the shorthand suffix scheme. - assertTrue(canAddPrinter(dialog, 'Test printer', '1::')); - }); - - test('ValidIPV6WithPort', () => { - const dialog = document.createElement('add-printer-manually-dialog'); - - assertTrue(canAddPrinter(dialog, 'Test printer', '[1:2:aa2:4]:12')); - assertTrue(canAddPrinter(dialog, 'Test printer', '[::255]:54')); - assertTrue(canAddPrinter(dialog, 'Test printer', '[1::]:7899')); - }); - - test('InvalidIPV6', () => { - const dialog = document.createElement('add-printer-manually-dialog'); - - assertFalse(canAddPrinter(dialog, 'Test printer', '1:2:3:4:5:6:7:8:9')); - assertFalse(canAddPrinter(dialog, 'Test printer', '1:2:3:aa:a1245:2')); - assertFalse(canAddPrinter(dialog, 'Test printer', '1:2:3:za:2')); - assertFalse(canAddPrinter(dialog, 'Test printer', '1:::22')); - assertFalse(canAddPrinter(dialog, 'Test printer', '1::2::3')); - }); - - test('ValidHostname', () => { - const dialog = document.createElement('add-printer-manually-dialog'); - - assertTrue(canAddPrinter(dialog, 'Test printer', 'hello-world.com')); - assertTrue(canAddPrinter(dialog, 'Test printer', 'hello.world.com:12345')); - }); - - test('InvalidHostname', () => { - const dialog = document.createElement('add-printer-manually-dialog'); - - assertFalse(canAddPrinter(dialog, 'Test printer', 'helloworld!123.com')); - assertFalse(canAddPrinter(dialog, 'Test printer', 'helloworld123-.com')); - assertFalse(canAddPrinter(dialog, 'Test printer', '-helloworld123.com')); - }); - - /** - * Test that clicking on Add opens the model select page. - */ - test('ValidAddOpensModelSelection', async () => { - // Starts in add manual dialog. - const addDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addDialog); - flush(); - fillAddManuallyDialog(addDialog); - - const button = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( - '.action-button'); - assertTrue(!!button); - button.click(); - flush(); - - // Upon rejection, show model. - await cupsPrintersBrowserProxy.whenCalled( - 'getCupsPrinterManufacturersList'); - await flushTasks(); - // Showing model selection. - assertTrue(!!dialog.shadowRoot!.querySelector( - 'add-printer-manufacturer-model-dialog')); - - assertTrue(dialog.get('showManufacturerDialog_')); - assertFalse(dialog.get('showManuallyAddDialog_')); - }); - - /** - * Test that when getPrinterInfo fails for a generic reason, the general error - * message is shown. - */ - test('GetPrinterInfoFailsGeneralError', async () => { - // Starts in add manual dialog. - const addDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addDialog); - flush(); - - fillAddManuallyDialog(addDialog); - - // Make the getPrinterInfo fail for a generic error. - cupsPrintersBrowserProxy.setGetPrinterInfoResult( - PrinterSetupResult.FATAL_ERROR); - - // Attempt to add the printer. - const button = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( - '.action-button'); - assertTrue(!!button); - button.click(); - flush(); - - // Upon rejection, show model. - await cupsPrintersBrowserProxy.whenCalled('getPrinterInfo'); - // The general error should be showing. - assertTrue(!!addDialog.get('errorText_')); - const generalErrorElement = - addDialog.shadowRoot!.querySelector('printer-dialog-error'); - assertTrue(!!generalErrorElement); - const errorContainer = - generalErrorElement.shadowRoot!.querySelector<HTMLElement>( - '#error-container'); - assertTrue(!!errorContainer); - assertFalse(errorContainer.hidden); - }); - - /** - * Test that when getPrinterInfo fails for an unreachable printer, the - printer - * address field is marked as invalid. - */ - test('GetPrinterInfoFailsUnreachableError', async () => { - // Starts in add manual dialog. - const addDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addDialog); - flush(); - - fillAddManuallyDialog(addDialog); - - // Make the getPrinterInfo fail for an unreachable printer. - cupsPrintersBrowserProxy.setGetPrinterInfoResult( - PrinterSetupResult.PRINTER_UNREACHABLE); - - // Attempt to add the printer. - const button = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( - '.action-button'); - assertTrue(!!button); - button.click(); - flush(); - - // Upon rejection, show model. - await cupsPrintersBrowserProxy.whenCalled('getPrinterInfo'); - - // The printer address input should be marked as invalid. - const printerAddressInput = - addDialog.shadowRoot!.querySelector<CrInputElement>( - '#printerAddressInput'); - assertTrue(!!printerAddressInput); - assertTrue(printerAddressInput.invalid); - }); - - /** - * Test that getModels isn't called with a blank query. - */ - test('NoBlankQueries', async () => { - // Starts in add manual dialog. - const addDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addDialog); - flush(); - fillAddManuallyDialog(addDialog); - - cupsPrintersBrowserProxy.manufacturers = { - success: false, - manufacturers: ['ManufacturerA', 'ManufacturerB', 'Chromites'], - }; - const button = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( - '.action-button'); - assertTrue(!!button); - button.click(); - flush(); - - await cupsPrintersBrowserProxy.whenCalled( - 'getCupsPrinterManufacturersList'); - - // Verify that getCupsPrinterModelList is not called. - assertEquals( - 0, cupsPrintersBrowserProxy.getCallCount('getCupsPrinterModelsList')); - - const modelDialog = dialog.shadowRoot!.querySelector( - 'add-printer-manufacturer-model-dialog'); - - // Manufacturer dialog has been rendered and the model list was not - // requested. We're done. - assertTrue(!!modelDialog); - }); - - /** - * Test that dialog cancellation is logged from the manufacturer screen for - * IPP printers. - */ - test('LogDialogCancelledIpp', async () => { - const makeAndModel = 'Printer Make And Model'; - // Start on add manual dialog. - dialog.dispatchEvent(new CustomEvent('open-manually-add-printer-dialog')); - flush(); - - // Populate the printer object. - dialog.newPrinter = { - isManaged: false, - ppdManufacturer: '', - ppdModel: '', - printerAddress: '192.168.1.13', - printerDescription: '', - printerId: '', - printerMakeAndModel: '', - printerName: 'Test Printer', - printerPPDPath: '', - printerPpdReference: { - userSuppliedPpdUrl: '', - effectiveMakeAndModel: '', - autoconf: false, - }, - printerProtocol: 'ipps', - printerQueue: 'moreinfohere', - printServerUri: '', - }; - - // Seed the getPrinterInfo response. We detect the make and model but it is - // not an autoconf printer. - cupsPrintersBrowserProxy.printerInfo = { - autoconf: false, - makeAndModel, - ppdRefUserSuppliedPpdUrl: 'ppd url', - ppdRefEffectiveMakeAndModel: 'Effective Make And Model', - ppdReferenceResolved: false, - }; - - // Press the add button to advance dialog. - const addDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addDialog); - flush(); - clickAddButton(addDialog); - - // Click cancel on the manufacturer dialog when it shows up then verify - // cancel was called with the appropriate parameters. - await cupsPrintersBrowserProxy.whenCalled( - 'getCupsPrinterManufacturersList'); - flush(); - // Cancel setup with the cancel button. - const modelDialog = dialog.shadowRoot!.querySelector( - 'add-printer-manufacturer-model-dialog'); - assertTrue(!!modelDialog); - clickCancelButton(modelDialog); - const printer = - await cupsPrintersBrowserProxy.whenCalled('cancelPrinterSetUp'); - assertTrue(!!printer, 'New printer is null'); - assertEquals(makeAndModel, printer.printerMakeAndModel); - }); - - /** - * Test that we are checking if a printer model has an EULA upon a model - * change. - */ - test('getEulaUrlGetsCalledOnModelChange', async () => { - // Start in add manual dialog. - const addDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addDialog); - flush(); - fillAddManuallyDialog(addDialog); - - const button = addDialog.shadowRoot!.querySelector<HTMLButtonElement>( - '.action-button'); - assertTrue(!!button); - button.click(); - flush(); - - const expectedEulaLink = 'chrome://os-credits/#google'; - const expectedManufacturer = 'Google'; - const expectedModel = 'printer'; - const expectedModel2 = 'newPrinter'; - const expectedModel3 = 'newPrinter2'; - - await cupsPrintersBrowserProxy.whenCalled( - 'getCupsPrinterManufacturersList'); - const modelDialog = dialog.shadowRoot!.querySelector( - 'add-printer-manufacturer-model-dialog'); - assertTrue(!!modelDialog); - - const urlElement = - modelDialog.shadowRoot!.querySelector<HTMLElement>('#eulaUrl'); - assertTrue(!!urlElement); - // Check that the EULA text is not shown. - assertTrue(urlElement.hidden); - - cupsPrintersBrowserProxy.setEulaUrl(expectedEulaLink); - - const manufacturerDropdown = - modelDialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( - '#manufacturerDropdown'); - assertTrue(!!manufacturerDropdown); - manufacturerDropdown.value = expectedManufacturer; - const modelDropdown = - modelDialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( - '#modelDropdown'); - assertTrue(!!modelDropdown); - modelDropdown.value = expectedModel; - await verifyGetEulaUrlWasCalled( - cupsPrintersBrowserProxy, expectedManufacturer, expectedModel); - - // Check that the EULA text is shown. - assertFalse(urlElement.hidden); - let anchor = urlElement.querySelector('a'); - assertTrue(!!anchor); - assertEquals(expectedEulaLink, anchor.href); - - resetGetEulaUrl(cupsPrintersBrowserProxy, '' /* eulaUrl */); - - // Change ppdModel and expect |getEulaUrl| to be called again. - modelDropdown.value = expectedModel2; - await verifyGetEulaUrlWasCalled( - cupsPrintersBrowserProxy, expectedManufacturer, expectedModel2); - - // Check that the EULA text is hidden. - assertTrue(urlElement.hidden); - - resetGetEulaUrl(cupsPrintersBrowserProxy, expectedEulaLink); - - // Change ppdModel and expect |getEulaUrl| to be called again. - modelDropdown.value = expectedModel3; - await verifyGetEulaUrlWasCalled( - cupsPrintersBrowserProxy, expectedManufacturer, expectedModel3); - assertFalse(urlElement.hidden); - anchor = urlElement.querySelector('a'); - assertTrue(!!anchor); - assertEquals(expectedEulaLink, anchor.href); - }); - - /** - * Test that the add button of the manufacturer dialog is disabled after - * clicking it. - */ - test('AddButtonDisabledAfterClicking', async () => { - // From the add manually dialog, click the add button to advance to the - // manufacturer dialog. - const addDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addDialog); - flush(); - fillAddManuallyDialog(addDialog); - clickAddButton(addDialog); - flush(); - - // Click the add button on the manufacturer dialog and then verify it is - // disabled. - await cupsPrintersBrowserProxy.whenCalled( - 'getCupsPrinterManufacturersList'); - const manufacturerDialog = dialog.shadowRoot!.querySelector( - 'add-printer-manufacturer-model-dialog'); - assertTrue(!!manufacturerDialog); - - // Populate the manufacturer and model fields to enable the add - // button. - const manufacturerDropdown = - manufacturerDialog.shadowRoot! - .querySelector<CrSearchableDropDownElement>( - '#manufacturerDropdown'); - assertTrue(!!manufacturerDropdown); - manufacturerDropdown.value = 'make'; - const modelDropdown = - manufacturerDialog.shadowRoot! - .querySelector<CrSearchableDropDownElement>('#modelDropdown'); - assertTrue(!!modelDropdown); - modelDropdown.value = 'model'; - - const addButton = - manufacturerDialog.shadowRoot!.querySelector<HTMLButtonElement>( - '#addPrinterButton'); - assertTrue(!!addButton); - assertFalse(addButton.disabled); - addButton.click(); - assertTrue(addButton.disabled); - }); - - /** - * The following tests check that clicking Enter button on the keyboard - from - * each input text field on the add-printer-manually-dialog will advance to - * the next dialog. - */ - test('PressEnterInPrinterNameInput', async () => { - mockAddPrinterInputKeyboardPress('#printerNameInput'); - - // Upon rejection, show model. - await cupsPrintersBrowserProxy.whenCalled( - 'getCupsPrinterManufacturersList'); - await flushTasks(); - // Showing model selection. - assertTrue(!!dialog.shadowRoot!.querySelector( - 'add-printer-manufacturer-model-dialog')); - assertTrue(dialog.get('showManufacturerDialog_')); - assertFalse(dialog.get('showManuallyAddDialog_')); - }); - - test('PressEnterInPrinterAddressInput', async () => { - mockAddPrinterInputKeyboardPress('#printerAddressInput'); - - // Upon rejection, show model. - await cupsPrintersBrowserProxy.whenCalled( - 'getCupsPrinterManufacturersList'); - await flushTasks(); - // Showing model selection. - assertNull( - dialog.shadowRoot!.querySelector('add-printer-configuring-dialog')); - assertTrue(dialog.get('showManufacturerDialog_')); - assertFalse(dialog.get('showManuallyAddDialog_')); - }); - - test('PressEnterInPrinterQueueInput', async () => { - mockAddPrinterInputKeyboardPress('#printerQueueInput'); - - // Upon rejection, show model. - await cupsPrintersBrowserProxy.whenCalled( - 'getCupsPrinterManufacturersList'); - await flushTasks(); - // Showing model selection. - assertTrue(!!dialog.shadowRoot!.querySelector( - 'add-printer-manufacturer-model-dialog')); - assertTrue(dialog.get('showManufacturerDialog_')); - assertFalse(dialog.get('showManuallyAddDialog_')); - }); - - /** - * Test that the add button of the manufacturer dialog is disabled when the - * manufacturer or model dropdown has an incorrect value. - */ - test('AddButtonDisabledAfterClicking', async () => { - // From the add manually dialog, click the add button to advance to the - // manufacturer dialog. - const addDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addDialog); - flush(); - fillAddManuallyDialog(addDialog); - clickAddButton(addDialog); - flush(); - - await cupsPrintersBrowserProxy.whenCalled( - 'getCupsPrinterManufacturersList'); - const manufacturerDialog = dialog.shadowRoot!.querySelector( - 'add-printer-manufacturer-model-dialog'); - assertTrue(!!manufacturerDialog); - - const manufacturerDropdown = - manufacturerDialog.shadowRoot! - .querySelector<CrSearchableDropDownElement>( - '#manufacturerDropdown'); - const modelDropdown = - manufacturerDialog.shadowRoot! - .querySelector<CrSearchableDropDownElement>('#modelDropdown'); - const addButton = - manufacturerDialog.shadowRoot!.querySelector<HTMLButtonElement>( - '#addPrinterButton'); - - assertTrue(!!manufacturerDropdown); - assertTrue(!!modelDropdown); - assertTrue(!!addButton); - // Set the starting values for manufacturer and model dropdown. - manufacturerDropdown.value = 'make'; - modelDropdown.value = 'model'; - assertFalse(addButton.disabled); - - // Mimic typing in random input. Make sure the Add button becomes - // disabled. - let searchElement = - manufacturerDropdown.shadowRoot!.querySelector<HTMLInputElement>( - '#search'); - assertTrue(!!searchElement); - searchElement.value = 'hlrRkJQkNsh'; - searchElement.dispatchEvent(new CustomEvent('input')); - assertTrue(addButton.disabled); - - // Then mimic typing in the original value to re-enable the Add - // button. - searchElement = - manufacturerDropdown.shadowRoot!.querySelector<HTMLInputElement>( - '#search'); - assertTrue(!!searchElement); - searchElement.value = 'make'; - searchElement.dispatchEvent(new CustomEvent('input')); - assertFalse(addButton.disabled); - - // Mimic typing in random input. Make sure the Add button becomes - // disabled. - searchElement = - modelDropdown.shadowRoot!.querySelector<HTMLInputElement>('#search'); - assertTrue(!!searchElement); - searchElement.value = 'hlrRkJQkNsh'; - searchElement.dispatchEvent(new CustomEvent('input')); - assertTrue(addButton.disabled); - - // Then mimic typing in the original value to re-enable the Add - // button. - searchElement = - modelDropdown.shadowRoot!.querySelector<HTMLInputElement>('#search'); - assertTrue(!!searchElement); - searchElement.value = 'model'; - searchElement.dispatchEvent(new CustomEvent('input')); - assertFalse(addButton.disabled); - }); - - test('Queue input is hidden when protocol is App Socket', () => { - const addDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addDialog); - let printerQueueInput = - addDialog.shadowRoot!.querySelector('#printerQueueInput'); - assertTrue(!!printerQueueInput); - const select = addDialog.shadowRoot!.querySelector('select'); - assertTrue(!!select); - - select.value = 'socket'; - select.dispatchEvent(new CustomEvent('change', {'bubbles': true})); - flush(); - - printerQueueInput = - addDialog.shadowRoot!.querySelector('#printerQueueInput'); - assertNull(printerQueueInput); - - select.value = 'http'; - select.dispatchEvent(new CustomEvent('change', {'bubbles': true})); - flush(); - - printerQueueInput = - addDialog.shadowRoot!.querySelector('#printerQueueInput'); - assertTrue(!!printerQueueInput); - }); -}); - -suite('EditPrinterDialog', () => { - let page: SettingsCupsPrintersElement; - let dialog: SettingsCupsEditPrinterDialogElement; - let cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy; - let wifi1: NetworkStateProperties; - - setup(async () => { - cupsPrintersBrowserProxy = new TestCupsPrintersBrowserProxy(); - - CupsPrintersBrowserProxyImpl.setInstanceForTesting( - cupsPrintersBrowserProxy); - - // Simulate internet connection. - wifi1 = OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi1'); - wifi1.connectionState = ConnectionStateType.kOnline; - - Router.getInstance().navigateTo(routes.CUPS_PRINTERS); - - page = document.createElement('settings-cups-printers'); - document.body.appendChild(page); - assertTrue(!!page); - page.onActiveNetworksChanged([wifi1]); - await flushTasks(); - }); - - teardown(() => { - cupsPrintersBrowserProxy.reset(); - page.remove(); - dialog.remove(); - }); - - function clickSaveButton(dialog: SettingsCupsEditPrinterDialogElement) { - assertTrue(!!dialog, 'Dialog is null for save'); - const saveButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - dialog.set('printerInfoChanged_', true); - assertTrue(!!saveButton, 'Button is null'); - assertFalse(saveButton.disabled); - saveButton.click(); - } - - function clickCancelButton(dialog: SettingsCupsEditPrinterDialogElement) { - assertTrue(!!dialog, 'Dialog is null for cancel'); - const cancelButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.cancel-button'); - assertTrue(!!cancelButton, 'Button is null'); - cancelButton.click(); - } - - /** - * Initializes a printer and sets that printer as the printer to be edited in - * the edit dialog. Opens the edit dialog. - */ - async function initializeAndOpenEditDialog( - name: string, address: string, id: string, autoconf: boolean, - manufacturer: string, model: string, protocol: string, - serverAddress: string): Promise<void> { - page.activePrinter = createCupsPrinterInfo(name, address, id); - page.activePrinter.printerPpdReference.autoconf = autoconf; - page.activePrinter.printerProtocol = protocol; - page.activePrinter.printServerUri = serverAddress; - cupsPrintersBrowserProxy.printerPpdMakeModel = { - ppdManufacturer: manufacturer, - ppdModel: model, - }; - // Trigger the edit dialog to open. - page.dispatchEvent(new CustomEvent('edit-cups-printer-details')); - flush(); - const element = - page.shadowRoot!.querySelector('settings-cups-edit-printer-dialog'); - assertTrue(!!element); - dialog = element; - // This proxy function gets called whenever the edit dialog is initialized. - await cupsPrintersBrowserProxy.whenCalled('getCupsPrinterModelsList'); - } - - /** - * Test that USB printers can be edited. - */ - test('USBPrinterCanBeEdited', async () => { - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'usb', /*serverAddress=*/ ''); - // Assert that the protocol is USB. - const selectElement = - dialog.shadowRoot!.querySelector<HTMLSelectElement>('.md-select'); - assertTrue(!!selectElement); - assertEquals('usb', selectElement.value); - - // Edit the printer name. - const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( - '.printer-name-input'); - assertTrue(!!nameField); - nameField.value = 'edited printer'; - nameField.dispatchEvent(new CustomEvent('input')); - - // Assert that the "Save" button is enabled. - const saveButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - assertTrue(!!saveButton); - assertFalse(saveButton.disabled); - }); - - /** - * Test that the save button is disabled when the printer address or name is - * invalid. - */ - test('EditPrinter', async () => { - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - const printerName = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerName'); - const printerAddress = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); - assertTrue(!!printerName); - assertTrue(!!printerAddress); - - const saveButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - assertTrue(!!saveButton); - assertTrue(saveButton.disabled); - - // Change printer name to something valid. - printerName.value = 'new printer name'; - printerName.dispatchEvent(new CustomEvent('input')); - assertFalse(saveButton.disabled); - - // Change printer address to something invalid. - printerAddress.value = 'abcdef:'; - assertTrue(saveButton.disabled); - - // Change back to something valid. - printerAddress.value = 'abcdef:1234'; - assertFalse(saveButton.disabled); - - // Change printer name to empty. - printerName.value = ''; - assertTrue(saveButton.disabled); - }); - - /** - * Test that closing the dialog does not persist the edits. - */ - test('CloseEditDialogDoesNotModifyActivePrinter', async () => { - const expectedName = 'Test Printer'; - const expectedAddress = '1.1.1.1'; - const expectedId = 'ID1'; - const expectedProtocol = 'ipp'; - await initializeAndOpenEditDialog( - /*name=*/ expectedName, /*address=*/ expectedAddress, - /*id=*/ expectedId, /*autoconf=*/ false, - /*manufacturer=*/ 'make', /*model=*/ 'model', - /*protocol=*/ expectedProtocol, /*serverAddress=*/ ''); - const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( - '.printer-name-input'); - assertTrue(!!nameField); - nameField.value = 'edited printer name'; - - const addressField = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); - assertTrue(!!addressField); - addressField.value = '9.9.9.9'; - - const protocolField = - dialog.shadowRoot!.querySelector<HTMLSelectElement>('.md-select'); - assertTrue(!!protocolField); - protocolField.value = 'http'; - - clickCancelButton(dialog); - - // Assert that activePrinter properties were not changed. - assertEquals(expectedName, dialog.activePrinter.printerName); - assertEquals(expectedAddress, dialog.activePrinter.printerAddress); - assertEquals(expectedProtocol, dialog.activePrinter.printerProtocol); - }); - - /** - * Test that editing the name field results in properly saving the new name. - */ - test('TestEditNameAndSave', async () => { - const expectedName = 'editedName'; - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( - '.printer-name-input'); - assertTrue(!!nameField); - nameField.value = expectedName; - - flush(); - clickSaveButton(dialog); - await cupsPrintersBrowserProxy.whenCalled('updateCupsPrinter'); - assertEquals(expectedName, dialog.activePrinter.printerName); - }); - - /** - * Test that editing various fields results in properly saving the new - * changes. - */ - test('TestEditFieldsAndSave', async () => { - const expectedAddress = '9.9.9.9'; - const expectedQueue = 'editedQueue'; - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - - // Editing more than just the printer name requires reconfiguring the - // printer. - const addressField = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); - assertTrue(!!addressField); - addressField.value = expectedAddress; - - const queueField = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerQueue'); - assertTrue(!!queueField); - queueField.value = expectedQueue; - - clickSaveButton(dialog); - await cupsPrintersBrowserProxy.whenCalled('reconfigureCupsPrinter'); - assertEquals(expectedAddress, dialog.activePrinter.printerAddress); - assertEquals(expectedQueue, dialog.activePrinter.printerQueue); - }); - - /** - * Test that editing an autoconf printer saves correctly. - */ - test('TestEditAutoConfFieldsAndSave', async () => { - const expectedAddress = '9.9.9.9'; - const expectedQueue = 'editedQueue'; - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ true, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - // Editing more than just the printer name requires reconfiguring the - // printer. - const addressField = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); - assertTrue(!!addressField); - addressField.value = expectedAddress; - - const queueField = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerQueue'); - assertTrue(!!queueField); - queueField.value = expectedQueue; - - clickSaveButton(dialog); - await cupsPrintersBrowserProxy.whenCalled('reconfigureCupsPrinter'); - assertEquals(expectedAddress, dialog.activePrinter.printerAddress); - assertEquals(expectedQueue, dialog.activePrinter.printerQueue); - }); - - /** - * Test that non-autoconf printers can select the make and model dropdowns. - */ - test('TestNonAutoConfPrintersCanSelectManufactureAndModel', async () => { - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - // Assert that the manufacturer and model drop-downs are shown. - const element = - dialog.shadowRoot!.querySelector<HTMLElement>('#makeAndModelSection'); - assertTrue(!!element); - assertFalse(element.hidden); - }); - - /** - * Test that autoconf printers cannot select their make/model - */ - test('TestAutoConfPrintersCannotSelectManufactureAndModel', async () => { - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ true, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - // Assert that the manufacturer and model drop-downs are hidden. - const element = - dialog.shadowRoot!.querySelector<DomIf>('#makeAndModelSection'); - assertTrue(!!element); - assertTrue(!element.if); - }); - - /** - * Test that changing the name enables the save button. - */ - test('TestChangingNameEnablesSaveButton', async () => { - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - const saveButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - assertTrue(!!saveButton); - assertTrue(saveButton.disabled); - - const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( - '.printer-name-input'); - assertTrue(!!nameField); - nameField.value = 'edited printer'; - nameField.dispatchEvent(new CustomEvent('input')); - - assertFalse(saveButton.disabled); - }); - - /** - * Test that changing the address enables the save button. - */ - test('TestChangingAddressEnablesSaveButton', async () => { - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - const saveButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - assertTrue(!!saveButton); - assertTrue(saveButton.disabled); - - const addressField = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); - assertTrue(!!addressField); - addressField.value = 'newAddress:789'; - addressField.dispatchEvent(new CustomEvent('input')); - - assertFalse(saveButton.disabled); - }); - - /** - * Test that changing the queue enables the save button. - */ - test('TestChangingQueueEnablesSaveButton', async () => { - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - const saveButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - assertTrue(!!saveButton); - assertTrue(saveButton.disabled); - - const queueField = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerQueue'); - assertTrue(!!queueField); - queueField.value = 'newqueueinfo'; - queueField.dispatchEvent(new CustomEvent('input')); - - assertFalse(saveButton.disabled); - }); - - /** - * Test that changing the protocol enables the save button. - */ - test('TestChangingProtocolEnablesSaveButton', async () => { - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - const saveButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - assertTrue(!!saveButton); - assertTrue(saveButton.disabled); - - const dropDown = - dialog.shadowRoot!.querySelector<HTMLSelectElement>('.md-select'); - assertTrue(!!dropDown); - dropDown.value = 'http'; - dropDown.dispatchEvent(new CustomEvent('change', {'bubbles': true})); - flush(); - assertFalse(saveButton.disabled); - }); - - /** - * Test that changing the model enables the save button. - */ - test('TestChangingModelEnablesSaveButton', async () => { - cupsPrintersBrowserProxy.manufacturers = { - success: true, - manufacturers: ['HP'], - }; - cupsPrintersBrowserProxy.models = {success: true, models: ['HP 910']}; - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - const saveButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - assertTrue(!!saveButton); - assertTrue(saveButton.disabled); - - const makeDropDown = - dialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( - '#printerPPDManufacturer'); - assertTrue(!!makeDropDown); - makeDropDown.value = 'HP'; - makeDropDown.dispatchEvent(new CustomEvent('change', {'bubbles': true})); - - await cupsPrintersBrowserProxy.whenCalled('getCupsPrinterModelsList'); - // Saving is disabled until a model is selected. - assertTrue(saveButton.disabled); - - const modelDropDown = - dialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( - '#printerPPDModel'); - assertTrue(!!modelDropDown); - modelDropDown.value = 'HP 910'; - modelDropDown.dispatchEvent(new CustomEvent('change', {'bubbles': true})); - - flush(); - assertFalse(saveButton.disabled); - }); - - /** - * Test that we are checking if a printer model has an EULA upon a model - * change. - */ - test('getEulaUrlGetsCalledOnModelChange', async () => { - const eulaLink = 'google.com'; - const expectedManufacturer = 'Google'; - const expectedModel = 'model'; - const expectedModel2 = 'newModel'; - const expectedModel3 = 'newModel2'; - - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - const urlElement = - dialog.shadowRoot!.querySelector<HTMLElement>('#eulaUrl'); - assertTrue(!!urlElement); - // Check that the EULA text is hidden. - assertTrue(urlElement.hidden); - - // 'getEulaUrl' is called as part of the initialization of the dialog, - // so we have to reset the resolver before the next call. - resetGetEulaUrl(cupsPrintersBrowserProxy, eulaLink); - - const makeDropDown = - dialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( - '#printerPPDManufacturer'); - assertTrue(!!makeDropDown); - makeDropDown.value = expectedManufacturer; - const modelDropdown = - dialog.shadowRoot!.querySelector<CrSearchableDropDownElement>( - '#printerPPDModel'); - assertTrue(!!modelDropdown); - modelDropdown.value = expectedModel; - - await verifyGetEulaUrlWasCalled( - cupsPrintersBrowserProxy, expectedManufacturer, expectedModel); - - // Check that the EULA text is shown. - assertFalse(urlElement.hidden); - - resetGetEulaUrl(cupsPrintersBrowserProxy, /*eulaUrl=*/ ''); - - // Change ppdModel and expect |getEulaUrl| to be called again. - modelDropdown.value = expectedModel2; - await verifyGetEulaUrlWasCalled( - cupsPrintersBrowserProxy, expectedManufacturer, expectedModel2); - - // Check that the EULA text is hidden. - assertTrue(urlElement.hidden); - - resetGetEulaUrl(cupsPrintersBrowserProxy, eulaLink); - - // Change ppdModel and expect |getEulaUrl| to be called again. - modelDropdown.value = expectedModel3; - await verifyGetEulaUrlWasCalled( - cupsPrintersBrowserProxy, expectedManufacturer, expectedModel3); - - // Check that the EULA text is shown again. - assertFalse(urlElement.hidden); - }); - - /** - * Test that editing the name is still supported when offline. - */ - test('OfflineEdit', async () => { - // Simulate connecting to a network with no internet connection. - wifi1.connectionState = ConnectionStateType.kConnected; - page.onActiveNetworksChanged([wifi1]); - flush(); - const expectedName = 'editedName'; - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ false, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', /*serverAddress=*/ ''); - const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( - '.printer-name-input'); - assertTrue(!!nameField); - nameField.value = expectedName; - nameField.dispatchEvent(new CustomEvent('input')); - - flush(); - - const saveButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - assertTrue(!!saveButton); - assertFalse(saveButton.disabled); - - clickSaveButton(dialog); - await cupsPrintersBrowserProxy.whenCalled('updateCupsPrinter'); - assertEquals(expectedName, dialog.activePrinter.printerName); - }); - - test('PrintServerPrinterEdit', async () => { - const expectedName = 'edited name'; - await initializeAndOpenEditDialog( - /*name=*/ 'name', /*address=*/ 'address', /*id=*/ 'id', - /*autoconf=*/ true, /*manufacturer=*/ 'make', - /*model=*/ 'model', /*protocol=*/ 'ipp', - /*serverAddress=*/ 'ipp://192.168.1.1:631'); - - // Verify the only the name field is not disabled. - const printerAddress = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerAddress'); - assertTrue(!!printerAddress); - assertTrue(printerAddress.disabled); - const selectElement = - dialog.shadowRoot!.querySelector<HTMLSelectElement>('.md-select'); - assertTrue(!!selectElement); - assertTrue(selectElement.disabled); - const printerQueue = - dialog.shadowRoot!.querySelector<HTMLInputElement>('#printerQueue'); - assertTrue(!!printerQueue); - assertTrue(printerQueue.disabled); - - const nameField = dialog.shadowRoot!.querySelector<HTMLInputElement>( - '.printer-name-input'); - assertTrue(!!nameField); - assertFalse(nameField.disabled); - - nameField.value = expectedName; - nameField.dispatchEvent(new CustomEvent('input')); - - flush(); - - const saveButton = - dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button'); - assertTrue(!!saveButton); - assertFalse(saveButton.disabled); - - clickSaveButton(dialog); - await cupsPrintersBrowserProxy.whenCalled('updateCupsPrinter'); - assertEquals(expectedName, dialog.activePrinter.printerName); - }); -}); - -suite('PrintServerTests', () => { - let page: SettingsCupsPrintersElement; - let dialog: SettingsCupsAddPrinterDialogElement; - let entryManager: CupsPrintersEntryManager; - let cupsPrintersBrowserProxy: TestCupsPrintersBrowserProxy; - - setup(async () => { - entryManager = CupsPrintersEntryManager.getInstance(); - setEntryManagerPrinters( - /*savedPrinters=*/[], /*automaticPrinters=*/[], - /*discoveredPrinters=*/[], /*printServerPrinters=*/[]); - - cupsPrintersBrowserProxy = new TestCupsPrintersBrowserProxy(); - - CupsPrintersBrowserProxyImpl.setInstanceForTesting( - cupsPrintersBrowserProxy); - - Router.getInstance().navigateTo(routes.CUPS_PRINTERS); - - page = document.createElement('settings-cups-printers'); - document.body.appendChild(page); - assertTrue(!!page); - const element = - page.shadowRoot!.querySelector('settings-cups-add-printer-dialog'); - assertTrue(!!element); - dialog = element; - - await flushTasks(); - }); - - teardown(() => { - cupsPrintersBrowserProxy.reset(); - page.remove(); - dialog.remove(); - }); - - function setEntryManagerPrinters( - savedPrinters: PrinterListEntry[], automaticPrinters: CupsPrinterInfo[], - discoveredPrinters: CupsPrinterInfo[], - printerServerPrinters: PrinterListEntry[]): void { - entryManager.setSavedPrintersList(savedPrinters); - entryManager.setNearbyPrintersList(automaticPrinters, discoveredPrinters); - entryManager.printServerPrinters = printerServerPrinters; - } - - /** - * Returns the print server dialog if it is available. - */ - function getPrintServerDialog(page: SettingsCupsPrintersElement): - AddPrintServerDialogElement { - assertTrue(!!page); - const element = - page.shadowRoot!.querySelector('settings-cups-add-printer-dialog'); - assertTrue(!!element); - dialog = element; - const addDialog = - dialog.shadowRoot!.querySelector('add-print-server-dialog'); - assertTrue(!!addDialog); - return addDialog; - } - - /** - * Opens the add print server dialog, inputs |address| with the specified - * |error|. Adds the print server and returns a promise for handling the add - * event. - * The promise returned when queryPrintServer is called. - */ - async function addPrintServer(address: string, error: number): Promise<void> { - // Open the add manual printe dialog. - assertTrue(!!page); - dialog.open(); - flush(); - - const addPrinterDialog = - dialog.shadowRoot!.querySelector('add-printer-manually-dialog'); - assertTrue(!!addPrinterDialog); - // Switch to Add print server dialog. - let button = addPrinterDialog.shadowRoot!.querySelector<HTMLButtonElement>( - '#print-server-button'); - assertTrue(!!button); - button.click(); - flush(); - const printServerDialog = - dialog.shadowRoot!.querySelector('add-print-server-dialog'); - assertTrue(!!printServerDialog); - - flush(); - cupsPrintersBrowserProxy.setQueryPrintServerResult(error); - await flushTasks(); - // Fill dialog with the server address. - const printServerAddressInput = - printServerDialog.shadowRoot!.querySelector<HTMLInputElement>( - '#printServerAddressInput'); - assertTrue(!!printServerAddressInput); - printServerAddressInput.value = address; - // Add the print server. - button = printServerDialog.shadowRoot!.querySelector<HTMLButtonElement>( - '.action-button'); - assertTrue(!!button); - // Button should not be disabled before clicking on it. - assertFalse(button.disabled); - button.click(); - // Clicking on the button should disable it. - assertTrue(button.disabled); - await cupsPrintersBrowserProxy.whenCalled('queryPrintServer'); - } - - function verifyErrorMessage(expectedError: string): void { - // Assert that the dialog did not close on errors. - const printServerDialog = getPrintServerDialog(page); - const dialogError = - printServerDialog.shadowRoot!.querySelector<PrinterDialogErrorElement>( - '#server-dialog-error'); - assertTrue(!!dialogError); - // Assert that the dialog error is displayed. - assertFalse(dialogError.hidden); - assertEquals( - loadTimeData.getString(expectedError), dialogError.get('errorText')); - } - - function verifyToastMessage( - expectedMessage: string, numPrinters: number): void { - // We always display the total number of printers found from a print - // server. - const toast = page.shadowRoot!.querySelector<CrToastElement>( - '#printServerErrorToast'); - assertTrue(!!toast); - assertTrue(toast.open); - assertEquals( - loadTimeData.getStringF(expectedMessage, numPrinters), - toast.textContent?.trim()); - } - - test('AddPrintServerIsSuccessful', async () => { - // Initialize the return result from adding a print server. - cupsPrintersBrowserProxy.printServerPrinters = { - printerList: [ - createCupsPrinterInfo('nameA', 'serverAddress', 'idA'), - createCupsPrinterInfo('nameB', 'serverAddress', 'idB'), - ], - }; - await addPrintServer('serverAddress', PrintServerResult.NO_ERRORS); - flush(); - verifyToastMessage('printServerFoundManyPrinters', /*numPrinters=*/ 2); - assertEquals(2, entryManager.printServerPrinters.length); - }); - - test('HandleDuplicateQueries', async () => { - // Initialize the return result from adding a print server. - cupsPrintersBrowserProxy.printServerPrinters = { - printerList: [ - createCupsPrinterInfo('nameA', 'serverAddress', 'idA'), - createCupsPrinterInfo('nameB', 'serverAddress', 'idB'), - ], - }; - - await flushTasks(); - // Simulate that a print server was queried previously. - setEntryManagerPrinters( - /*savedPrinters=*/[], /*nearbyPrinters=*/[], - /*discoveredPrinters=*/[], [ - createPrinterListEntry( - 'nameA', 'serverAddress', 'idA', PrinterType.PRINTSERVER), - createPrinterListEntry( - 'nameB', 'serverAddress', 'idB', PrinterType.PRINTSERVER), - ]); - flush(); - assertEquals(2, entryManager.printServerPrinters.length); - - // This will attempt to add duplicate print server printers. - // Matching printerId's are considered duplicates. - await addPrintServer('serverAddress', PrintServerResult.NO_ERRORS); - flush(); - - verifyToastMessage('printServerFoundManyPrinters', /*numPrinters=*/ 2); - // Assert that adding the same print server results in no new printers - // added to the entry manager. - assertEquals(2, entryManager.printServerPrinters.length); - const nearbyPrintersElement = - page.shadowRoot!.querySelector('settings-cups-nearby-printers'); - assertTrue(!!nearbyPrintersElement); - assertEquals(2, nearbyPrintersElement.nearbyPrinters.length); - }); - - test('HandleDuplicateSavedPrinters', async () => { - // Initialize the return result from adding a print server. - cupsPrintersBrowserProxy.printServerPrinters = { - printerList: [ - createCupsPrinterInfo('nameA', 'serverAddress', 'idA'), - createCupsPrinterInfo('nameB', 'serverAddress', 'idB'), - ], - }; - - await flushTasks(); - // Simulate that a print server was queried previously. - setEntryManagerPrinters( - /*savedPrinters=*/[], /*nearbyPrinters=*/[], - /*discoveredPrinters=*/[], [ - createPrinterListEntry( - 'nameA', 'serverAddress', 'idA', PrinterType.PRINTSERVER), - createPrinterListEntry( - 'nameB', 'serverAddress', 'idB', PrinterType.PRINTSERVER), - ]); - flush(); - assertEquals(2, entryManager.printServerPrinters.length); - - // Simulate adding a saved printer. - entryManager.setSavedPrintersList([createPrinterListEntry( - 'nameA', 'serverAddress', 'idA', PrinterType.SAVED)]); - flush(); - - // Simulate the underlying model changes. Nearby printers are also - // updated after changes to saved printers. - webUIListenerCallback( - 'on-nearby-printers-changed', /*automaticPrinter=*/[], - /*discoveredPrinters=*/[]); - await flushTasks(); - - // Verify that we now only have 1 printer in print server printers - // list. - assertEquals(1, entryManager.printServerPrinters.length); - const nearbyPrintersElement = - page.shadowRoot!.querySelector('settings-cups-nearby-printers'); - assertTrue(!!nearbyPrintersElement); - assertEquals(1, nearbyPrintersElement.nearbyPrinters.length); - // Verify we correctly removed the duplicate printer, 'idA', since - // it exists in the saved printer list. Expect only 'idB' in - // the print server printers list. - assertEquals( - 'idB', entryManager.printServerPrinters[0]!.printerInfo.printerId); - }); - - test('AddPrintServerAddressError', async () => { - cupsPrintersBrowserProxy.printServerPrinters = {printerList: []}; - await addPrintServer('serverAddress', PrintServerResult.INCORRECT_URL); - flush(); - const printServerDialog = getPrintServerDialog(page); - // Assert that the dialog did not close on errors. - assertTrue(!!printServerDialog); - const printServerAddressInput = - printServerDialog.shadowRoot!.querySelector<CrInputElement>( - '#printServerAddressInput'); - assertTrue(!!printServerAddressInput); - // Assert that the address input field is invalid. - assertTrue(printServerAddressInput.invalid); - }); - - test('AddPrintServerConnectionError', async () => { - cupsPrintersBrowserProxy.printServerPrinters = {printerList: []}; - await addPrintServer('serverAddress', PrintServerResult.CONNECTION_ERROR); - flush(); - verifyErrorMessage('printServerConnectionError'); - }); - - test('AddPrintServerReachableServerButIppResponseError', async () => { - cupsPrintersBrowserProxy.printServerPrinters = {printerList: []}; - await addPrintServer( - 'serverAddress', PrintServerResult.CANNOT_PARSE_IPP_RESPONSE); - flush(); - verifyErrorMessage('printServerConfigurationErrorMessage'); - }); - - test('AddPrintServerReachableServerButHttpResponseError', async () => { - cupsPrintersBrowserProxy.printServerPrinters = {printerList: []}; - await addPrintServer('serverAddress', PrintServerResult.HTTP_ERROR); - flush(); - verifyErrorMessage('printServerConfigurationErrorMessage'); - }); -});
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 78e8a44..f2e4399 100644 --- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js +++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -685,6 +685,13 @@ ], ['OsPrintingPage', 'os_printing_page/os_printing_page_test.js'], [ + 'OsPrintingPageCupsPrintServer', 'os_printing_page/cups_print_server_test.js' + ], + [ + 'OsPrintingPageCupsPrinterDialog', + 'os_printing_page/cups_printer_dialog_test.js' + ], + [ 'OsPrintingPageCupsPrinterLandingPage', 'os_printing_page/cups_printer_landing_page_test.js', {enabled: ['ash::features::kPrinterSettingsPrinterStatus']}
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts index 06a2466d..832af9b 100644 --- a/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts +++ b/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts
@@ -7,6 +7,7 @@ import {AppElement} from 'chrome://customize-chrome-side-panel.top-chrome/app.js'; import {BackgroundCollection, CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerRemote, CustomizeChromePageRemote, CustomizeChromeSection} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome.mojom-webui.js'; import {CustomizeChromeApiProxy} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome_api_proxy.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; import {assertEquals, assertGE, assertTrue} from 'chrome://webui-test/chai_assert.js'; import {TestMock} from 'chrome://webui-test/test_mock.js'; @@ -130,4 +131,20 @@ assertTrue( customizeChromeApp.$.overviewPage.classList.contains('iron-selected')); }); + + [true, false].forEach((flagEnabled) => { + suite(`ExtensionCardEnabled_${flagEnabled}`, () => { + suiteSetup(() => { + loadTimeData.overrideValues({ + 'extensionsCardEnabled': flagEnabled, + }); + }); + + test(`extension card does ${flagEnabled ? '' : 'not '}show`, async () => { + assertEquals( + !!customizeChromeApp.shadowRoot!.querySelector('#extensions'), + flagEnabled); + }); + }); + }); });
diff --git a/chrome/test/variations/fixtures/seed_locator.py b/chrome/test/variations/fixtures/seed_locator.py index cd62a64..f5189ed 100644 --- a/chrome/test/variations/fixtures/seed_locator.py +++ b/chrome/test/variations/fixtures/seed_locator.py
@@ -11,7 +11,7 @@ from enum import Enum from typing import Mapping -_DEFAULT_SEED_PATH=os.path.join(TEST_DATA_DIR, 'variations_seed') +_DEFAULT_SEED_PATH=os.path.join(TEST_DATA_DIR, 'variations_seed.json') class SeedName(Enum): # The default seed that is similar to what end-user would receive. @@ -20,6 +20,7 @@ # intentionally to crash Chrome. CRASH = 2 + @attr.attrs() class SeedLocator: all_seed_files: Mapping[SeedName, str] = attr.attrib() @@ -35,12 +36,14 @@ ) return seed_file + def pytest_addoption(parser): parser.addoption('--seed-file', default=_DEFAULT_SEED_PATH, dest='seed_file', help='The seed file used to run with the test.') + @pytest.fixture def seed_locator(pytestconfig) -> SeedLocator: """Returns the locator that finds a seed file using names.
diff --git a/chrome/test/variations/run_variations_tests.py b/chrome/test/variations/run_variations_tests.py index 16be927..b93862c 100755 --- a/chrome/test/variations/run_variations_tests.py +++ b/chrome/test/variations/run_variations_tests.py
@@ -13,6 +13,7 @@ os.path.join(os.path.dirname(__file__), *([os.pardir] * 3))) sys.path.append(SRC_DIR) +sys.path.append(os.getcwd()) from testing.scripts import common if __name__ == "__main__":
diff --git a/chrome/test/variations/smoke_test.py b/chrome/test/variations/smoke_test.py index 647ebd6..2e89a4a 100644 --- a/chrome/test/variations/smoke_test.py +++ b/chrome/test/variations/smoke_test.py
@@ -54,10 +54,9 @@ url = (f'http://localhost:{local_http_server.server_port}') # Launch Chrome normally. with driver_factory.create_driver() as driver: - driver.get("chrome://version") - version = WebDriverWait(driver, 5).until( - EC.presence_of_element_located((By.ID, 'version'))) - logging.info('Chrome version: %s', version.text) + driver.get(url) + WebDriverWait(driver, 5).until( + EC.presence_of_element_located((By.TAG_NAME, 'body'))) # Expecting Chrome to crash, setting a shorter startup timeout. # The default is 60 seconds, changing to 5 seconds.
diff --git a/chrome/updater/lock_mac.mm b/chrome/updater/lock_mac.mm index 4d7adc7..f88fd7e 100644 --- a/chrome/updater/lock_mac.mm +++ b/chrome/updater/lock_mac.mm
@@ -14,9 +14,9 @@ #include <string> #include <utility> +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" #include "base/logging.h" -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_mach_port.h" #include "base/strings/strcat.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" @@ -41,14 +41,14 @@ // // Returns the receive right if the right was successfully acquired. If the // right cannot be acquired for any reason, returns an invalid right instead. -base::mac::ScopedMachReceiveRight TryAcquireReceive( - const base::mac::ScopedMachSendRight& bootstrap_right, +base::apple::ScopedMachReceiveRight TryAcquireReceive( + const base::apple::ScopedMachSendRight& bootstrap_right, const std::string& service_name) { VLOG(2) << __func__; - base::mac::ScopedMachReceiveRight target_right; + base::apple::ScopedMachReceiveRight target_right; kern_return_t check_in_result = bootstrap_check_in( bootstrap_right.get(), service_name.c_str(), - base::mac::ScopedMachReceiveRight::Receiver(target_right).get()); + base::apple::ScopedMachReceiveRight::Receiver(target_right).get()); if (check_in_result != KERN_SUCCESS) { // Log error reports for all errors other than BOOTSTRAP_NOT_PRIVILEGED. // BOOTSTRAP_NOT_PRIVILEGED is not logged because it just means that another @@ -60,7 +60,7 @@ BOOTSTRAP_VLOG(2, check_in_result) << " lock already held: " << service_name; } - return base::mac::ScopedMachReceiveRight(); + return base::apple::ScopedMachReceiveRight(); } return target_right; } @@ -78,7 +78,7 @@ class ScopedLockImpl { public: // Constructs a ScopedLockImpl from a receive right. - explicit ScopedLockImpl(base::mac::ScopedMachReceiveRight receive_right); + explicit ScopedLockImpl(base::apple::ScopedMachReceiveRight receive_right); // Releases the receive right (and therefore releases the lock). ~ScopedLockImpl() = default; @@ -89,10 +89,11 @@ private: // The Mach port representing the held lock itself. We only care about // service ownership; no messages are transferred with this port. - base::mac::ScopedMachReceiveRight receive_right_; + base::apple::ScopedMachReceiveRight receive_right_; }; -ScopedLockImpl::ScopedLockImpl(base::mac::ScopedMachReceiveRight receive_right) +ScopedLockImpl::ScopedLockImpl( + base::apple::ScopedMachReceiveRight receive_right) : receive_right_(std::move(receive_right)) { mach_port_type_t port_type = 0; kern_return_t port_check_result = @@ -130,18 +131,18 @@ // when launching a privileged process. It's repeated here so processes // launched via sudo (such as the integration test helper) can reach the // system-scope locks. - base::mac::ScopedMachSendRight bootstrap_right = - base::mac::RetainMachSendRight(bootstrap_port); + base::apple::ScopedMachSendRight bootstrap_right = + base::apple::RetainMachSendRight(bootstrap_port); if (!geteuid()) { // Move the initial bootstrap right into `next_right` so the first loop is // not a special case. base::ScopedGeneric calls `abort()` if you `reset` it // to what it already holds, so this has to be a move, not a retain. - base::mac::ScopedMachSendRight next_right(bootstrap_right.release()); + base::apple::ScopedMachSendRight next_right(bootstrap_right.release()); while (bootstrap_right.get() != next_right.get()) { bootstrap_right.reset(next_right.release()); kern_return_t bootstrap_err = bootstrap_parent( bootstrap_right.get(), - base::mac::ScopedMachSendRight::Receiver(next_right).get()); + base::apple::ScopedMachSendRight::Receiver(next_right).get()); if (bootstrap_err != KERN_SUCCESS) { BOOTSTRAP_LOG(ERROR, bootstrap_err) << "can't bootstrap_parent in ScopedLock::Create (for euid 0)"; @@ -153,7 +154,7 @@ } // Make one try to acquire the lock, even if the timeout is zero or negative. - base::mac::ScopedMachReceiveRight receive_right( + base::apple::ScopedMachReceiveRight receive_right( TryAcquireReceive(bootstrap_right, service_name.c_str())); base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
diff --git a/chrome/updater/util/win_util.cc b/chrome/updater/util/win_util.cc index 0181571..c6a98b73b 100644 --- a/chrome/updater/util/win_util.cc +++ b/chrome/updater/util/win_util.cc
@@ -5,6 +5,7 @@ #include "chrome/updater/util/win_util.h" #include <aclapi.h> +#include <combaseapi.h> #include <objidl.h> #include <regstr.h> #include <shellapi.h> @@ -1049,4 +1050,11 @@ .AppendASCII(PRODUCT_FULLNAME_STRING); } +bool IsSTA() { + APTTYPE apt_type = APTTYPE_CURRENT; + APTTYPEQUALIFIER apt_type_qualifier = APTTYPEQUALIFIER_NONE; + return SUCCEEDED(::CoGetApartmentType(&apt_type, &apt_type_qualifier)) && + (apt_type == APTTYPE_STA || apt_type == APTTYPE_MAINSTA); +} + } // namespace updater
diff --git a/chrome/updater/util/win_util.h b/chrome/updater/util/win_util.h index 3e64f1e..ed9623d7 100644 --- a/chrome/updater/util/win_util.h +++ b/chrome/updater/util/win_util.h
@@ -405,6 +405,12 @@ // Does not create the directory if it does not exist. [[nodiscard]] absl::optional<base::FilePath> GetInstallDirectoryX86( UpdaterScope scope); + +// Returns `true` if COM is initialized as a single-threaded apartment. +// Otherwise, returns `false` if COM in not initialized, or its apartment type +// is not a form of STA. +[[nodiscard]] bool IsSTA(); + } // namespace updater #endif // CHROME_UPDATER_UTIL_WIN_UTIL_H_
diff --git a/chrome/updater/util/win_util_unittest.cc b/chrome/updater/util/win_util_unittest.cc index 739f264..a2e17b73 100644 --- a/chrome/updater/util/win_util_unittest.cc +++ b/chrome/updater/util/win_util_unittest.cc
@@ -22,18 +22,23 @@ #include "base/path_service.h" #include "base/process/launch.h" #include "base/process/process.h" +#include "base/run_loop.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/waitable_event.h" #include "base/system/sys_info.h" +#include "base/task/single_thread_task_runner.h" +#include "base/task/thread_pool.h" #include "base/test/bind.h" #include "base/test/gmock_expected_support.h" +#include "base/test/task_environment.h" #include "base/test/test_timeouts.h" #include "base/threading/platform_thread.h" #include "base/win/atl.h" #include "base/win/registry.h" +#include "base/win/scoped_com_initializer.h" #include "base/win/scoped_handle.h" #include "base/win/scoped_localalloc.h" #include "chrome/updater/test/integration_tests_impl.h" @@ -522,4 +527,24 @@ DeleteAppClientStateKey(GetTestScope(), base::ASCIIToWide(kTestAppID)); } +TEST(WinUtil, IsSTA) { + base::test::TaskEnvironment task_environment; + + // The unit test main has initialized this thread already as an MTA. + EXPECT_FALSE(IsSTA()); + + base::RunLoop run_loop; + base::ThreadPool::CreateCOMSTATaskRunner( + {base::TaskPriority::USER_VISIBLE, + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, + base::SingleThreadTaskRunnerThreadMode::DEDICATED) + ->PostTaskAndReply( + FROM_HERE, base::BindOnce([] { + base::win::ScopedCOMInitializer com_sta; + EXPECT_TRUE(IsSTA()); + }), + base::BindLambdaForTesting([&run_loop]() { run_loop.Quit(); })); + run_loop.Run(); +} + } // namespace updater
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd index 6ee87bae..ba107d5 100644 --- a/chromeos/chromeos_strings.grd +++ b/chromeos/chromeos_strings.grd
@@ -359,6 +359,13 @@ <message name="IDS_TABLET_MULTITASK_MENU_NUDGE_TEXT" desc="Text that is shown when the tablet multitask menu nudge is displayed."> Swipe down for more layout options </message> + <!-- ARC SDK Version Labels--> + <message name="IDS_ARC_SDK_VERSION_LABEL" desc="Label for Android SDK Version bundled with ARC"> + SDK Version: + </message> + <message name="IDS_ARC_SDK_VERSION_UNKNOWN" desc="Message displayed when the Android SDK Version can not be found"> + Unknown + </message> <if expr="chromeos_ash"> <!-- The following strings are located here for accessibility from both //ash and //chrome --> <message name="IDS_ENABLE_BLUETOOTH" desc="The message to display in the network list when Tether is enabled but Bluetooth is disabled."> @@ -3814,15 +3821,6 @@ <message name="IDS_FIRMWARE_PROCEED_UPDATE_CONFIRMATION" desc="Information text to users that to proceed with the firmware update that they need to click on the Next button."> To proceed with the update, click Next. </message> - - <!-- ARC SDK Version Labels--> - <message name="IDS_ARC_SDK_VERSION_LABEL" desc="Label for Android SDK Version bundled with ARC"> - SDK Version: - </message> - <message name="IDS_ARC_SDK_VERSION_UNKNOWN" desc="Message displayed when the Android SDK Version can not be found"> - Unknown - </message> - <!-- Feedback Tool--> <!-- Search Page --> <message name="IDS_FEEDBACK_TOOL_CONTINUE_BUTTON_LABEL" desc="Label of the continue button."> Continue
diff --git a/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc b/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc index f4104c46..1e747bd 100644 --- a/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc +++ b/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc
@@ -208,8 +208,9 @@ } // Insert the buttons left to right. - if (custom_button) + if (custom_button) { custom_button_ = AddChildView(std::move(custom_button)); + } menu_button_ = new views::FrameCaptionButton( base::BindRepeating(&FrameCaptionButtonContainerView::MenuButtonPressed, @@ -363,8 +364,9 @@ void FrameCaptionButtonContainerView::UpdateBorderlessModeEnabled( bool enabled) { - if (is_borderless_mode_enabled_ == enabled) + if (is_borderless_mode_enabled_ == enabled) { return; + } // In borderless mode, the windowing controls will be drawn in web content, // so similarly to hiding the title bar, also the caption button container @@ -456,8 +458,9 @@ // This ensures that the first frame of the animation to show the size button // pushes the buttons to the left of the size button into the center. - if (tablet_mode_animation_->is_animating()) + if (tablet_mode_animation_->is_animating()) { AnimationProgressed(tablet_mode_animation_.get()); + } #if DCHECK_IS_ON() if (close_button_->GetVisible()) { @@ -483,8 +486,9 @@ AnimationProgressed(animation); double current_value = tablet_mode_animation_->GetCurrentValue(); - if (current_value == 0.0) + if (current_value == 0.0) { size_button_->SetVisible(false); + } } void FrameCaptionButtonContainerView::AnimationProgressed( @@ -524,8 +528,9 @@ // minimize button but it can also include a PWA menu button. int previous_x = 0; for (auto* button : children()) { - if (button == size_button_) + if (button == size_button_) { break; + } button->SetX(previous_x + x_slide); previous_x += button->width(); } @@ -565,8 +570,9 @@ (animate == Animate::kYes) ? views::FrameCaptionButton::Animate::kYes : views::FrameCaptionButton::Animate::kNo; auto it = button_icon_map_.find(icon); - if (it != button_icon_map_.end()) + if (it != button_icon_map_.end()) { button->SetImage(icon, fcb_animate, *it->second); + } } void FrameCaptionButtonContainerView::UpdateSizeButton() { @@ -712,8 +718,10 @@ aura::Window* window = GetWidget()->GetNativeWindow(); if (window->GetProperty(kWindowStateTypeKey) == WindowStateType::kFloated) { + base::RecordAction(base::UserMetricsAction(kUnFloatUserAction)); FloatControllerBase::Get()->UnsetFloat(window); } else { + base::RecordAction(base::UserMetricsAction(kFloatUserAction)); FloatControllerBase::Get()->SetFloat(window, FloatStartLocation::kBottomRight); } @@ -762,8 +770,9 @@ views::FrameCaptionButton* closest_button = nullptr; for (size_t i = 0; i < std::size(buttons); ++i) { views::FrameCaptionButton* button = buttons[i]; - if (!button || !button->GetVisible()) + if (!button || !button->GetVisible()) { continue; + } gfx::Point center_point = button->GetLocalBounds().CenterPoint(); views::View::ConvertPointToTarget(button, this, ¢er_point); @@ -785,13 +794,15 @@ minimize_button_, size_button_, float_button_, close_button_}; for (views::FrameCaptionButton* button : buttons) { - if (!button) + if (!button) { continue; + } views::Button::ButtonState new_state = views::Button::STATE_NORMAL; - if (button == to_hover) + if (button == to_hover) { new_state = views::Button::STATE_HOVERED; - else if (button == to_press) + } else if (button == to_press) { new_state = views::Button::STATE_PRESSED; + } button->SetState(new_state); } }
diff --git a/chromeos/ui/frame/header_view.cc b/chromeos/ui/frame/header_view.cc index 28bd34bf..ee940b4 100644 --- a/chromeos/ui/frame/header_view.cc +++ b/chromeos/ui/frame/header_view.cc
@@ -78,8 +78,6 @@ target_widget, (frame_view ? static_cast<views::View*>(frame_view) : this), caption_button_container_); - - UpdateHeaderRoundedCorners(); } void HeaderView::Init() { @@ -165,6 +163,10 @@ : views::PaintInfo::ScaleType::kScaleWithEdgeSnapping); } +void HeaderView::SetHeaderCornerRadius(int radius) { + frame_header_->SetHeaderCornerRadius(radius); +} + void HeaderView::Layout() { did_layout_ = true; header_content_view_->SetBoundsRect(GetLocalBounds()); @@ -194,14 +196,6 @@ DCHECK_EQ(target_widget_->GetNativeWindow(), window); - // Headers as part of frames in chromeOS have rounded frames for certain - // window states. If these states changes, we need to update the rounded - // corners accordingly. See `chromeos::GetFrameCornerRadius()` for more - // details. - if (CanPropertyEffectFrameRadius(key)) { - UpdateHeaderRoundedCorners(); - } - if (key == aura::client::kAvatarIconKey) { gfx::ImageSkia* const avatar_icon = window->GetProperty(aura::client::kAvatarIconKey); @@ -388,15 +382,6 @@ caption_button_container_->SetVisible(should_paint_); } -void HeaderView::UpdateHeaderRoundedCorners() { - if (!target_widget_) { - return; - } - - frame_header_->SetHeaderCornerRadius( - GetFrameCornerRadius(target_widget_->GetNativeWindow())); -} - BEGIN_METADATA(HeaderView, views::View) END_METADATA
diff --git a/chromeos/ui/frame/header_view.h b/chromeos/ui/frame/header_view.h index 8a0c09c..ef506a0 100644 --- a/chromeos/ui/frame/header_view.h +++ b/chromeos/ui/frame/header_view.h
@@ -15,10 +15,8 @@ #include "base/scoped_observation.h" #include "chromeos/ui/frame/frame_header.h" #include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_delegate.h" -#include "third_party/skia/include/core/SkColor.h" #include "ui/aura/window.h" #include "ui/aura/window_observer.h" -#include "ui/base/ui_base_types.h" #include "ui/display/display_observer.h" #include "ui/views/view.h" @@ -94,6 +92,8 @@ void SetWidthInPixels(int width_in_pixels); + void SetHeaderCornerRadius(int radius); + // views::View: void Layout() override; void ChildPreferredSizeChanged(views::View* child) override; @@ -144,7 +144,6 @@ void UpdateBackButton(); void UpdateCenterButton(); void UpdateCaptionButtonsVisibility(); - void UpdateHeaderRoundedCorners(); // The widget that the caption buttons act on. raw_ptr<views::Widget, ExperimentalAsh> target_widget_; @@ -152,11 +151,7 @@ // A callback to run when |in_immersive_mode_| changes. base::RepeatingClosure immersive_mode_changed_callback_; - // Helper for painting the header. The exact type of FrameHeader will depend - // on the type of window: In Mash, Chrome Browser windows use - // CustomFrameHeader which is aware of theming. In classic Ash, Chrome Browser - // windows won't use HeaderView at all. In either configuration, non Browser - // windows will use DefaultFrameHeader. + // Helper for painting the header. std::unique_ptr<chromeos::DefaultFrameHeader> frame_header_; raw_ptr<views::ImageView, ExperimentalAsh> avatar_icon_ = nullptr;
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h b/chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h index bf0d4c04..160501ca 100644 --- a/chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h +++ b/chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h
@@ -36,6 +36,29 @@ constexpr char kPartialSplitDurationHistogramName[] = "Ash.Window.PartialSplitDuration"; +// Used to record when the user pressed the fullscreen option in the multitask +// menu. +constexpr char kFullscreenUserAction[] = "MultitaskMenu_Fullscreen"; + +// Used to record when the user pressed the exit fullscreen option in the +// multitask menu. +constexpr char kExitFullscreenUserAction[] = "MultitaskMenu_Exit_Fullscreen"; + +// Used to record when the user pressed the float option in the multitask menu. +constexpr char kFloatUserAction[] = "MultitaskMenu_Float"; + +// Used to record when the user pressed the unfloat option in the multitask +// menu. +constexpr char kUnFloatUserAction[] = "MultitaskMenu_UnFloat"; + +// Used to record when the user half splits to primary direction. +constexpr char kHalfSplitPrimaryUserAction[] = + "MultitaskMenu_HalfSplit_Primary"; + +// Used to record when the user half splits to secondary direction. +constexpr char kHalfSplitSecondaryUserAction[] = + "MultitaskMenu_HalfSplit_Secondary"; + // Used to record when the user partial splits to one third. constexpr char kPartialSplitOneThirdUserAction[] = "MultitaskMenu_PartialSplit_OneThird";
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc b/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc index df690fa..8b7abe8 100644 --- a/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc +++ b/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
@@ -403,6 +403,9 @@ window_, direction, kDefaultSnapRatio, SnapController::SnapRequestSource::kWindowLayoutMenu); close_callback_.Run(); + base::RecordAction(base::UserMetricsAction( + direction == SnapDirection::kPrimary ? kHalfSplitPrimaryUserAction + : kHalfSplitSecondaryUserAction)); RecordMultitaskMenuActionType(MultitaskMenuActionType::kHalfSplitButton); } @@ -425,15 +428,20 @@ void MultitaskMenuView::FullScreenButtonPressed() { auto* widget = views::Widget::GetWidgetForNativeWindow(window_); - widget->SetFullscreen(!widget->IsFullscreen()); + const bool is_fullscreen = widget->IsFullscreen(); + widget->SetFullscreen(!is_fullscreen); close_callback_.Run(); + base::RecordAction(base::UserMetricsAction( + is_fullscreen ? kExitFullscreenUserAction : kFullscreenUserAction)); RecordMultitaskMenuActionType(MultitaskMenuActionType::kFullscreenButton); } void MultitaskMenuView::FloatButtonPressed() { if (window_->GetProperty(kWindowStateTypeKey) == WindowStateType::kFloated) { + base::RecordAction(base::UserMetricsAction(kUnFloatUserAction)); FloatControllerBase::Get()->UnsetFloat(window_); } else { + base::RecordAction(base::UserMetricsAction(kFloatUserAction)); FloatControllerBase::Get()->SetFloat( window_, is_reversed_ ? FloatStartLocation::kBottomLeft : FloatStartLocation::kBottomRight);
diff --git a/chromeos/ui/frame/non_client_frame_view_base.cc b/chromeos/ui/frame/non_client_frame_view_base.cc index a6316765..f8d5c73a 100644 --- a/chromeos/ui/frame/non_client_frame_view_base.cc +++ b/chromeos/ui/frame/non_client_frame_view_base.cc
@@ -4,12 +4,14 @@ #include "chromeos/ui/frame/non_client_frame_view_base.h" +#include <memory> + #include "chromeos/ui/base/tablet_state.h" #include "chromeos/ui/base/window_properties.h" #include "chromeos/ui/frame/default_frame_header.h" #include "chromeos/ui/frame/frame_utils.h" +#include "chromeos/ui/frame/header_view.h" #include "ui/aura/client/aura_constants.h" -#include "ui/base/metadata/metadata_header_macros.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/color/color_id.h" #include "ui/gfx/geometry/rect.h" @@ -22,7 +24,6 @@ NonClientFrameViewBase::OverlayView::OverlayView(HeaderView* header_view) : header_view_(header_view) { - AddChildView(header_view); SetEventTargeter(std::make_unique<views::ViewTargeter>(this)); } @@ -59,12 +60,15 @@ END_METADATA NonClientFrameViewBase::NonClientFrameViewBase(views::Widget* frame) - : frame_(frame), - header_view_(new HeaderView(frame, this)), - overlay_view_(new OverlayView(header_view_)) { + : frame_(frame) { DCHECK(frame_); - header_view_->Init(); + auto header_view = std::make_unique<HeaderView>(frame_, this); + header_view->Init(); + + auto overlay_view = std::make_unique<OverlayView>(header_view.get()); + header_view_ = overlay_view->AddChildView(std::move(header_view)); + overlay_view_ = overlay_view.get(); // |header_view_| is set as the non client view's overlay view so that it can // overlay the web contents in immersive fullscreen. @@ -72,7 +76,7 @@ // would avoid the need to expose an "overlay view" concept on the // cross-platform class, and might allow for simpler creation/ownership/ // plumbing. - frame->non_client_view()->SetOverlayView(overlay_view_); + frame->non_client_view()->SetOverlayView(overlay_view.release()); UpdateDefaultFrameColors(); }
diff --git a/components/BUILD.gn b/components/BUILD.gn index b780d50..1e9b121 100644 --- a/components/BUILD.gn +++ b/components/BUILD.gn
@@ -623,7 +623,7 @@ "//components/metrics/structured/mojom:unit_tests", "//components/ownership:unit_tests", "//components/user_manager:unit_tests", - "//components/variations/cros:unit_tests", + "//components/variations/cros_evaluate_seed:unit_tests", ] }
diff --git a/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.cc b/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.cc index c5814dc..f58e04af 100644 --- a/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.cc +++ b/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.cc
@@ -42,4 +42,9 @@ VirtualCardEnrollmentManager::ShowVirtualCardEnrollBubble(); } +void TestVirtualCardEnrollmentManager:: + OnVirtualCardEnrollmentBubbleCancelled() { + VirtualCardEnrollmentManager::OnVirtualCardEnrollmentBubbleCancelled(); +} + } // namespace autofill
diff --git a/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.h b/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.h index ca6f187..68b8041d 100644 --- a/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.h +++ b/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.h
@@ -83,6 +83,8 @@ void Reset() override; void ShowVirtualCardEnrollBubble() override; + void OnVirtualCardEnrollmentBubbleCancelled(); + private: AutofillClient::PaymentsRpcResult result_;
diff --git a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc index 31c894e..d2d5b7f4 100644 --- a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc +++ b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc
@@ -357,6 +357,15 @@ weak_ptr_factory_.GetWeakPtr())); } +void VirtualCardEnrollmentManager::OnVirtualCardEnrollmentBubbleCancelled() { + if (base::FeatureList::IsEnabled( + features::kAutofillEnableUpdateVirtualCardEnrollment)) { + AddStrikeToBlockOfferingVirtualCardEnrollment(base::NumberToString( + state_.virtual_card_enrollment_fields.credit_card.instrument_id())); + } + Reset(); +} + void VirtualCardEnrollmentManager::OnRiskDataLoadedForVirtualCard( const std::string& risk_data) { state_.risk_data = risk_data; @@ -551,13 +560,4 @@ return true; } -void VirtualCardEnrollmentManager::OnVirtualCardEnrollmentBubbleCancelled() { - if (base::FeatureList::IsEnabled( - features::kAutofillEnableUpdateVirtualCardEnrollment)) { - AddStrikeToBlockOfferingVirtualCardEnrollment(base::NumberToString( - state_.virtual_card_enrollment_fields.credit_card.instrument_id())); - } - Reset(); -} - } // namespace autofill
diff --git a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h index b292f07..ccc0b3e 100644 --- a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h +++ b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h
@@ -251,6 +251,9 @@ absl::optional<VirtualCardEnrollmentUpdateResponseCallback> virtual_card_enrollment_update_response_callback_; + // Cancels the entire Virtual Card enrollment process. + void OnVirtualCardEnrollmentBubbleCancelled(); + private: friend class VirtualCardEnrollmentManagerTest; FRIEND_TEST_ALL_PREFIXES(VirtualCardEnrollmentManagerTest, @@ -273,10 +276,6 @@ StrikeDatabase_SettingsPageNotBlocked); FRIEND_TEST_ALL_PREFIXES(VirtualCardEnrollmentManagerTest, VirtualCardEnrollmentFields_LastShow); - FRIEND_TEST_ALL_PREFIXES(VirtualCardEnrollmentManagerTest, - RequiredDelaySinceLastStrike_ExpOn); - FRIEND_TEST_ALL_PREFIXES(VirtualCardEnrollmentManagerTest, - RequiredDelaySinceLastStrike_ExpOff); // Called once the risk data is loaded. The |risk_data| will be used with // |state_|'s |virtual_card_enrollment_fields|'s |credit_card|'s @@ -330,9 +329,6 @@ const payments::PaymentsClient::GetDetailsForEnrollmentResponseDetails& get_details_for_enrollment_response_details); - // Cancels the entire Virtual Card Enrollment process. - void OnVirtualCardEnrollmentBubbleCancelled(); - FRIEND_TEST_ALL_PREFIXES(VirtualCardEnrollmentManagerTest, Enroll); FRIEND_TEST_ALL_PREFIXES(VirtualCardEnrollmentManagerTest, OnDidGetDetailsForEnrollResponse);
diff --git a/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc b/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc index 5fe93a1f..d52f23c 100644 --- a/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc +++ b/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc
@@ -835,13 +835,9 @@ } // Test to ensure that the required delay since the last strike is respected -// before Chrome offers another virtual card enrollment for the card, when the -// |kAutofillEnforceDelaysInStrikeDatabase| is enabled. -TEST_F(VirtualCardEnrollmentManagerTest, RequiredDelaySinceLastStrike_ExpOn) { - base::test::ScopedFeatureList scoped_feature_list; +// before Chrome offers another virtual card enrollment for the card. +TEST_F(VirtualCardEnrollmentManagerTest, RequiredDelaySinceLastStrike) { base::HistogramTester histogram_tester; - scoped_feature_list.InitAndEnableFeature( - features::kAutofillEnforceDelaysInStrikeDatabase); SetUpStrikeDatabaseTest(); TestAutofillClock test_autofill_clock(AutofillClock::Now()); VirtualCardEnrollmentProcessState* state = @@ -868,12 +864,9 @@ base::NumberToString(card_->instrument_id()), VirtualCardEnrollmentSource::kDownstream)); - // Advances the clock for - // |kAutofillVirtualCardEnrollDelayInStrikeDatabaseInDays - 1| days. Verifies + // Advances the clock for `kEnrollmentEnforcedDelayInDays` - 1 days. Verifies // that enrollment should still be blocked. - test_autofill_clock.Advance(base::Days( - features::kAutofillVirtualCardEnrollDelayInStrikeDatabaseInDays.Get() - - 1)); + test_autofill_clock.Advance(base::Days(kEnrollmentEnforcedDelayInDays - 1)); EXPECT_TRUE( virtual_card_enrollment_manager_->ShouldBlockVirtualCardEnrollment( base::NumberToString(card_->instrument_id()), @@ -898,42 +891,6 @@ VirtualCardEnrollmentSource::kDownstream, 2); } -// Test to ensure that the required delay since last strike is respected before -// Chrome offers another virtual card enrollment for the card, when the -// |kAutofillEnforceDelaysInStrikeDatabase| is disabled. -TEST_F(VirtualCardEnrollmentManagerTest, RequiredDelaySinceLastStrike_ExpOff) { - base::test::ScopedFeatureList scoped_feature_list; - base::HistogramTester histogram_tester; - scoped_feature_list.InitAndDisableFeature( - features::kAutofillEnforceDelaysInStrikeDatabase); - SetUpStrikeDatabaseTest(); - TestAutofillClock test_autofill_clock; - test_autofill_clock.SetNow(AutofillClock::Now()); - VirtualCardEnrollmentProcessState* state = - virtual_card_enrollment_manager_->GetVirtualCardEnrollmentProcessState(); - SetUpCard(); - card_->set_instrument_id(11223344); - state->virtual_card_enrollment_fields.credit_card = *card_; - - virtual_card_enrollment_manager_->InitVirtualCardEnroll( - *card_, VirtualCardEnrollmentSource::kDownstream, absl::nullopt, - virtual_card_enrollment_manager_->AutofillClientIsPresent() ? user_prefs() - : nullptr, - base::DoNothing()); - - // Logs one strike for the card and makes sure that the enrollment offer is - // not blocked. - virtual_card_enrollment_manager_->OnVirtualCardEnrollmentBubbleCancelled(); - - histogram_tester.ExpectUniqueSample( - "Autofill.StrikeDatabase.NthStrikeAdded.VirtualCardEnrollment", - /*sample=*/1, /*count=*/1); - EXPECT_FALSE( - virtual_card_enrollment_manager_->ShouldBlockVirtualCardEnrollment( - base::NumberToString(card_->instrument_id()), - VirtualCardEnrollmentSource::kDownstream)); -} - #endif // !BUILDFLAG(IS_IOS) TEST_F(VirtualCardEnrollmentManagerTest, Metrics_LatencySinceUpstream) {
diff --git a/components/autofill/core/browser/single_field_form_fill_router_unittest.cc b/components/autofill/core/browser/single_field_form_fill_router_unittest.cc index 167e56b3..dad60e1e 100644 --- a/components/autofill/core/browser/single_field_form_fill_router_unittest.cc +++ b/components/autofill/core/browser/single_field_form_fill_router_unittest.cc
@@ -240,9 +240,6 @@ for (bool test_field_should_autocomplete : {true, false}) { SCOPED_TRACE(testing::Message() << "test_field_should_autocomplete = " << test_field_should_autocomplete); - base::test::ScopedFeatureList feature_list; - feature_list.InitWithFeatures( - {features::kAutofillFillMerchantPromoCodeFields}, {}); auto suggestions_handler = std::make_unique<MockSuggestionsHandler>(); test_field_.should_autocomplete = test_field_should_autocomplete; @@ -264,9 +261,6 @@ // Ensure that the router routes to AutocompleteHistoryManager for this // OnGetSingleFieldSuggestions call if MerchantPromoCodeManager is not present. TEST_F(SingleFieldFormFillRouterTest, MerchantPromoCodeManagerNotPresent) { - base::test::ScopedFeatureList feature_list; - feature_list.InitWithFeatures( - {features::kAutofillFillMerchantPromoCodeFields}, {}); auto suggestions_handler = std::make_unique<MockSuggestionsHandler>(); // This also invalidates the WeakPtr that the `single_field_form_fill_router_` @@ -292,9 +286,6 @@ // OnGetSingleFieldSuggestions call if // MerchantPromoCodeManager::OnGetSingleFieldSuggestions() returns false. TEST_F(SingleFieldFormFillRouterTest, MerchantPromoCodeManagerReturnedFalse) { - base::test::ScopedFeatureList feature_list; - feature_list.InitWithFeatures( - {features::kAutofillFillMerchantPromoCodeFields}, {}); auto suggestions_handler = std::make_unique<MockSuggestionsHandler>(); // Mock MerchantPromoCodeManager::OnGetSingleFieldSuggestions() returning @@ -346,9 +337,6 @@ TEST_F( SingleFieldFormFillRouterTest, FieldNotEligibleForAnySingleFieldFormFiller_OnGetSingleFieldSuggestions) { - base::test::ScopedFeatureList feature_list; - feature_list.InitWithFeatures( - {features::kAutofillFillMerchantPromoCodeFields}, {}); auto suggestions_handler = std::make_unique<MockSuggestionsHandler>(); EXPECT_CALL(*merchant_promo_code_manager_, OnGetSingleFieldSuggestions)
diff --git a/components/autofill/core/browser/strike_databases/payments/virtual_card_enrollment_strike_database.cc b/components/autofill/core/browser/strike_databases/payments/virtual_card_enrollment_strike_database.cc index d73f6dc..3a8d533 100644 --- a/components/autofill/core/browser/strike_databases/payments/virtual_card_enrollment_strike_database.cc +++ b/components/autofill/core/browser/strike_databases/payments/virtual_card_enrollment_strike_database.cc
@@ -10,6 +10,8 @@ namespace autofill { +namespace { + // Limit the number of cards for which strikes are collected constexpr size_t kMaxStrikeEntities = 50; @@ -18,10 +20,12 @@ // The maximum number of strikes before we stop showing virtual card enrollment // dialogs. -int kCardMaximumStrikes = 3; +constexpr int kCardMaximumStrikes = 3; // The number of days until strikes expire for virtual card enrollment. -int kDaysUntilCardStrikeExpiry = 180; +constexpr int kDaysUntilCardStrikeExpiry = 180; + +} // namespace VirtualCardEnrollmentStrikeDatabase::VirtualCardEnrollmentStrikeDatabase( StrikeDatabaseBase* strike_database) @@ -71,13 +75,8 @@ absl::optional<base::TimeDelta> VirtualCardEnrollmentStrikeDatabase::GetRequiredDelaySinceLastStrike() const { - if (base::FeatureList::IsEnabled( - features::kAutofillEnforceDelaysInStrikeDatabase)) { - return absl::optional<base::TimeDelta>(base::Days( - features::kAutofillVirtualCardEnrollDelayInStrikeDatabaseInDays.Get())); - } - - return absl::nullopt; + return absl::optional<base::TimeDelta>( + base::Days(kEnrollmentEnforcedDelayInDays)); } } // namespace autofill
diff --git a/components/autofill/core/browser/strike_databases/payments/virtual_card_enrollment_strike_database.h b/components/autofill/core/browser/strike_databases/payments/virtual_card_enrollment_strike_database.h index afbf95e..75cc708 100644 --- a/components/autofill/core/browser/strike_databases/payments/virtual_card_enrollment_strike_database.h +++ b/components/autofill/core/browser/strike_databases/payments/virtual_card_enrollment_strike_database.h
@@ -14,6 +14,10 @@ namespace autofill { +// The delay required since the last strike before offering another virtual card +// enrollment attempt. +constexpr int kEnrollmentEnforcedDelayInDays = 7; + // Implementation of StrikeDatabaseIntegratorBase for virtual card enrollment // dialogs. class VirtualCardEnrollmentStrikeDatabase
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc index ac9ea90..f0ec75b9 100644 --- a/components/autofill/core/common/autofill_payments_features.cc +++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -188,18 +188,6 @@ "AutofillEnableVirtualCardMetadata", base::FEATURE_DISABLED_BY_DEFAULT); -// When enabled, if the previous feature offer was declined, a delay will be -// added before Chrome attempts to show offer again. -BASE_FEATURE(kAutofillEnforceDelaysInStrikeDatabase, - "AutofillEnforceDelaysInStrikeDatabase_LAUNCHED", - base::FEATURE_ENABLED_BY_DEFAULT); - -// When enabled, Autofill will attempt to fill merchant promo/coupon/gift code -// fields when data is available. -BASE_FEATURE(kAutofillFillMerchantPromoCodeFields, - "AutofillFillMerchantPromoCodeFields_LAUNCHED", - base::FEATURE_ENABLED_BY_DEFAULT); - // When enabled, legal term of save card view and virtual card enroll view will // be moved before action buttons and icon will be moved after titles in those // views. @@ -267,13 +255,6 @@ "AutofillUseEloRegexForBinMatching_LAUNCHED", base::FEATURE_ENABLED_BY_DEFAULT); -// The delay required since the last strike before offering another virtual card -// enrollment attempt. -const base::FeatureParam<int> - kAutofillVirtualCardEnrollDelayInStrikeDatabaseInDays{ - &kAutofillEnforceDelaysInStrikeDatabase, - "autofill_virtual_card_enroll_delay_in_strike_database_in_days", 7}; - #if BUILDFLAG(IS_IOS) // When enabled, use two '•' when displaying the last four digits of a credit // card number. (E.g., '•• 8888' rather than '•••• 8888').
diff --git a/components/autofill/core/common/autofill_payments_features.h b/components/autofill/core/common/autofill_payments_features.h index cfa0470..a7492e2 100644 --- a/components/autofill/core/common/autofill_payments_features.h +++ b/components/autofill/core/common/autofill_payments_features.h
@@ -42,8 +42,6 @@ BASE_DECLARE_FEATURE(kAutofillEnableVirtualCardFidoEnrollment); BASE_DECLARE_FEATURE(kAutofillEnableVirtualCardManagementInDesktopSettingsPage); BASE_DECLARE_FEATURE(kAutofillEnableVirtualCardMetadata); -BASE_DECLARE_FEATURE(kAutofillEnforceDelaysInStrikeDatabase); -BASE_DECLARE_FEATURE(kAutofillFillMerchantPromoCodeFields); BASE_DECLARE_FEATURE(kAutofillMoveLegalTermsAndIconForNewCardEnrollment); BASE_DECLARE_FEATURE(kAutofillOfferToSaveCardWithSameLastFour); BASE_DECLARE_FEATURE(kAutofillParseVcnCardOnFileStandaloneCvcFields); @@ -54,8 +52,6 @@ BASE_DECLARE_FEATURE(kAutofillUpstreamAuthenticatePreflightCall); BASE_DECLARE_FEATURE(kAutofillUpstreamUseAlternateSecureDataType); BASE_DECLARE_FEATURE(kAutofillUseEloRegexForBinMatching); -extern const base::FeatureParam<int> - kAutofillVirtualCardEnrollDelayInStrikeDatabaseInDays; #if BUILDFLAG(IS_IOS) BASE_DECLARE_FEATURE(kAutofillUseTwoDotsForLastFourDigits);
diff --git a/components/browser_ui/widget/android/java/res/layout/empty_state_view.xml b/components/browser_ui/widget/android/java/res/layout/empty_state_view.xml index 2ffde02..0e4e05a 100644 --- a/components/browser_ui/widget/android/java/res/layout/empty_state_view.xml +++ b/components/browser_ui/widget/android/java/res/layout/empty_state_view.xml
@@ -5,43 +5,50 @@ found in the LICENSE file. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/empty_state_container" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingBottom="@dimen/selectable_list_toolbar_height" + android:clipToPadding="false" android:layout_marginEnd="@dimen/default_list_row_padding" android:layout_marginStart="@dimen/default_list_row_padding" - android:gravity="center" - android:padding="@dimen/card_padding" - android:scaleType="fitCenter" - android:autoSizeTextType="uniform" - android:orientation="vertical"> + android:fillViewport="true"> - <ImageView - android:id="@+id/empty_state_icon" - android:contentDescription="@null" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/empty_state_text_title" - android:maxWidth="@dimen/empty_state_text_width" - android:paddingTop="@dimen/empty_state_heading_padding_top" - android:layout_width="wrap_content" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:textAlignment="center" - android:textAppearance="@style/TextAppearance.Headline2Thick.Secondary" - app:leading="@dimen/headline2_size_leading"/> + android:gravity="center" + android:scaleType="fitCenter" + android:autoSizeTextType="uniform" + android:orientation="vertical"> - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/empty_state_text_description" - android:maxWidth="@dimen/empty_state_text_width" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingTop="@dimen/empty_state_subheading_padding_top" - android:textAlignment="center" - android:textAppearance="@style/TextAppearance.TextMedium.Secondary" - app:leading="@dimen/text_size_medium_leading"/> -</LinearLayout> + <ImageView + android:id="@+id/empty_state_icon" + android:contentDescription="@null" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <org.chromium.ui.widget.TextViewWithLeading + android:id="@+id/empty_state_text_title" + android:maxWidth="@dimen/empty_state_text_width" + android:paddingTop="@dimen/empty_state_heading_padding_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAlignment="center" + android:textAppearance="@style/TextAppearance.Headline2Thick.Secondary" + app:leading="@dimen/headline2_size_leading"/> + + <org.chromium.ui.widget.TextViewWithLeading + android:id="@+id/empty_state_text_description" + android:maxWidth="@dimen/empty_state_text_width" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="@dimen/empty_state_subheading_padding_top" + android:textAlignment="center" + android:textAppearance="@style/TextAppearance.TextMedium.Secondary" + app:leading="@dimen/text_size_medium_leading"/> + </LinearLayout> +</ScrollView>
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java index 0e2384c..3e63aa8 100644 --- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java +++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java
@@ -4,7 +4,9 @@ package org.chromium.components.browser_ui.widget.gesture; +import androidx.activity.BackEventCompat; import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import org.chromium.base.supplier.ObservableSupplier; import org.chromium.base.supplier.ObservableSupplierImpl; @@ -99,4 +101,22 @@ default ObservableSupplier<Boolean> getHandleBackPressChangedSupplier() { return new ObservableSupplierImpl<>(); } + + /** + * API 34+ only. Triggered when a back press press is cancelled. In this case, + * {@link #handleBackPress()} must not be triggered any more. + */ + default void handleOnBackCancelled() {} + + /** + * API 34+ only. Triggered after a back press is started + * ({@link #handleOnBackStarted(BackEventCompat)}) and before a back press is released + * (either {@link #handleBackPress()} or {@link #handleOnBackCancelled()}) + */ + default void handleOnBackProgressed(@NonNull BackEventCompat backEvent) {} + + /** + * API 34+ only. Triggered when a back press event is initialized. + */ + default void handleOnBackStarted(@NonNull BackEventCompat backEvent) {} }
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListLayout.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListLayout.java index 3e76c07..f0af18a 100644 --- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListLayout.java +++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/selectable_list/SelectableListLayout.java
@@ -256,13 +256,6 @@ // Initialize and inflate empty state view stub. ViewStub emptyViewStub = findViewById(R.id.empty_state_view_stub); View emptyStateView = emptyViewStub.inflate(); - int bottomMargin = getContext().getResources().getDimensionPixelSize( - R.dimen.selectable_list_toolbar_height) - / 2; - FrameLayout.LayoutParams emptyViewParams = - (FrameLayout.LayoutParams) emptyStateView.getLayoutParams(); - emptyViewParams.bottomMargin = bottomMargin; - emptyStateView.setLayoutParams(emptyViewParams); // Initialize empty state resource. mEmptyView = emptyStateView.findViewById(R.id.empty_state_text_title);
diff --git a/components/content_settings/browser/ui/cookie_controls_util.cc b/components/content_settings/browser/ui/cookie_controls_util.cc index 3a0048c..c39d8f3 100644 --- a/components/content_settings/browser/ui/cookie_controls_util.cc +++ b/components/content_settings/browser/ui/cookie_controls_util.cc
@@ -13,32 +13,12 @@ #include "ui/base/ui_base_features.h" namespace content_settings { -namespace { - -// Get the local time, round down to midnight on the current day, and then load -// this time as a base::Time UTC time. This is unusual, but when we find the -// TimeDelta::InDays() within GetDaysToExpiration() we want to make sure we're -// counting actual days in the local timezone, not actual days in UTC time. -base::Time LocalMidnightAsUTCTime(base::Time t) { - base::Time::Exploded exploded; - t.LocalExplode(&exploded); - exploded.hour = 0; - exploded.minute = 0; - exploded.second = 0; - exploded.millisecond = 0; - - base::Time out; - bool result = base::Time::FromUTCExploded(exploded, &out); - DCHECK(result); - return out; -} - -} // namespace // static int CookieControlsUtil::GetDaysToExpiration(base::Time expiration) { - const base::Time midnight_today = LocalMidnightAsUTCTime(base::Time::Now()); - const base::Time midnight_expiration = LocalMidnightAsUTCTime(expiration); + // TODO(crbug.com/1446230): Apply DST corrections. + const base::Time midnight_today = base::Time::Now().LocalMidnight(); + const base::Time midnight_expiration = expiration.LocalMidnight(); return (midnight_expiration - midnight_today).InDays(); }
diff --git a/components/content_settings/browser/ui/cookie_controls_util_unittest.cc b/components/content_settings/browser/ui/cookie_controls_util_unittest.cc index aee8ad6..e7bf33d 100644 --- a/components/content_settings/browser/ui/cookie_controls_util_unittest.cc +++ b/components/content_settings/browser/ui/cookie_controls_util_unittest.cc
@@ -4,7 +4,6 @@ #include "components/content_settings/browser/ui/cookie_controls_util.h" -#include "base/test/icu_test_util.h" #include "base/time/time_override.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -13,8 +12,6 @@ namespace { using ::testing::Eq; -const char kNewYorkTime[] = "America/New_York"; - struct StaticOverrideTime { static base::Time Now() { return override_time; } static base::Time override_time; @@ -52,6 +49,8 @@ EXPECT_THAT(CookieControlsUtil::GetDaysToExpiration( GetTime("Tue, 15 Nov 2023 23:59:59")), Eq(0)); + // EXPECT_THAT(task_environment_.GetMockClock()->Now(), Eq(GetTime("Tue, 15 + // Nov 2023 12:45:26"))); } // Return value of 1 represents times that occur tomorrow. @@ -112,85 +111,4 @@ Eq(-2)); } -// On Fuchsia posix local time functions always use UTC. -#if !BUILDFLAG(IS_FUCHSIA) -// For 2023 DST for New York timezone is from March 12 to November 5. -TEST_F(CookieControlsUtilTest, DSTOverlap) { - base::test::ScopedRestoreDefaultTimezone scoped_timezone(kNewYorkTime); - { - // 23:00, spring forward to next day, so we actually expire on the 6th day - // for 5 day period. - auto time_override = GetScopedNow("Sat, 11 Mar 2023 23:00:01"); - EXPECT_THAT(CookieControlsUtil::GetDaysToExpiration(base::Time::Now() + - base::Days(5)), - Eq(6)); - } - { - // 22:00, spring forward, but not quite to the next day. - auto time_override = GetScopedNow("Sat, 11 Mar 2023 22:00:00"); - EXPECT_THAT(CookieControlsUtil::GetDaysToExpiration(base::Time::Now() + - base::Days(5)), - Eq(5)); - } - { - // 00:00, fall back to the previous day (so day 4 for a 5 day period). - auto time_override = GetScopedNow("Sat, 4 Nov 2023 00:00:00"); - EXPECT_THAT(CookieControlsUtil::GetDaysToExpiration(base::Time::Now() + - base::Days(5)), - Eq(4)); - } - { - // 01:00, fall back but not quite to the previous day. - auto time_override = GetScopedNow("Sat, 4 Nov 2023 01:00:00"); - EXPECT_THAT(CookieControlsUtil::GetDaysToExpiration(base::Time::Now() + - base::Days(5)), - Eq(5)); - } -} -#endif - -TEST_F(CookieControlsUtilTest, NoDSTOverlapOutsideDST) { - base::test::ScopedRestoreDefaultTimezone scoped_timezone(kNewYorkTime); - // Without DST overlap expiration should be in days equal to the actual number - // of 24hr periods we expire from now. - { - auto time_override = GetScopedNow("Sat, 1 Jan 2023 00:00:00"); - for (int i = 0; i < 60; i++) { - EXPECT_THAT(CookieControlsUtil::GetDaysToExpiration(base::Time::Now() + - base::Days(i)), - Eq(i)); - } - } - { - auto time_override = GetScopedNow("Sat, 1 Jan 2023 23:00:00"); - for (int i = 0; i < 60; i++) { - EXPECT_THAT(CookieControlsUtil::GetDaysToExpiration(base::Time::Now() + - base::Days(i)), - Eq(i)); - } - } -} - -TEST_F(CookieControlsUtilTest, NoDSTOverlapInDST) { - base::test::ScopedRestoreDefaultTimezone scoped_timezone(kNewYorkTime); - // Without DST overlap expiration should be in days equal to the actual number - // of 24hr periods we expire from now. - { - auto time_override = GetScopedNow("Sat, 1 Apr 2023 00:00:00"); - for (int i = 0; i < 60; i++) { - EXPECT_THAT(CookieControlsUtil::GetDaysToExpiration(base::Time::Now() + - base::Days(i)), - Eq(i)); - } - } - { - auto time_override = GetScopedNow("Sat, 1 Apr 2023 23:00:00"); - for (int i = 0; i < 60; i++) { - EXPECT_THAT(CookieControlsUtil::GetDaysToExpiration(base::Time::Now() + - base::Days(i)), - Eq(i)); - } - } -} - } // namespace content_settings
diff --git a/components/crash/core/app/crashpad.h b/components/crash/core/app/crashpad.h index 26f7338..24ccf0d 100644 --- a/components/crash/core/app/crashpad.h +++ b/components/crash/core/app/crashpad.h
@@ -17,7 +17,7 @@ #include "third_party/abseil-cpp/absl/types/optional.h" #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_mach_port.h" +#include "base/apple/scoped_mach_port.h" #endif #if BUILDFLAG(IS_WIN)
diff --git a/components/exo/layer_tree_frame_sink_holder.cc b/components/exo/layer_tree_frame_sink_holder.cc index d3854ca5..c45b330 100644 --- a/components/exo/layer_tree_frame_sink_holder.cc +++ b/components/exo/layer_tree_frame_sink_holder.cc
@@ -96,8 +96,8 @@ lifetime_manager->AddObserver(holder.release()); } -void LayerTreeFrameSinkHolder::SubmitCompositorFrame( - viz::CompositorFrame frame) { +void LayerTreeFrameSinkHolder::SubmitCompositorFrame(viz::CompositorFrame frame, + bool submit_now) { if (!reactive_frame_submission_) { SubmitCompositorFrameToRemote(&frame); return; @@ -107,7 +107,7 @@ DiscardCachedFrame(&frame); - if (!ShouldSubmitFrameNow()) { + if (!ShouldSubmitFrameNow() && !submit_now) { cached_frame_ = std::move(frame); return; } @@ -322,6 +322,10 @@ frame_sink_->SubmitCompositorFrame(std::move(*frame), /*hit_test_data_changed=*/true); + // TODO(crbug.com/1473386): Push an object to + // `pending_discarded_frame_notifications_` instead of using the counter here, + // s.t. we don't have to wait until this counter drop to zero before + // `SendDiscardedFrameNotifications()`, and frame_acks are properly ordered. pending_submit_frames_++; } @@ -434,11 +438,16 @@ void LayerTreeFrameSinkHolder::ProcessFirstPendingBeginFrame( viz::CompositorFrame* frame) { - DCHECK(!pending_begin_frames_.empty()); + if (!pending_begin_frames_.empty()) { + frame->metadata.begin_frame_ack = + pending_begin_frames_.front().begin_frame_ack; + pending_begin_frames_.pop(); + return; + } + // Submit an unsolicited frame. frame->metadata.begin_frame_ack = - pending_begin_frames_.front().begin_frame_ack; - pending_begin_frames_.pop(); + viz::BeginFrameAck::CreateManualAckWithDamage(); } bool LayerTreeFrameSinkHolder::ShouldSubmitFrameNow() const {
diff --git a/components/exo/layer_tree_frame_sink_holder.h b/components/exo/layer_tree_frame_sink_holder.h index 927a7179..a77cae94 100644 --- a/components/exo/layer_tree_frame_sink_holder.h +++ b/components/exo/layer_tree_frame_sink_holder.h
@@ -64,7 +64,8 @@ // If a frame is submitted "now" (meaning before returning to event loop) // via SubmitCompositorFrame(), whether it needs full damage. bool NeedsFullDamageForNextFrame() const { return cached_frame_.has_value(); } - void SubmitCompositorFrame(viz::CompositorFrame frame); + void SubmitCompositorFrame(viz::CompositorFrame frame, + bool submit_now = false); void SetLocalSurfaceId(const viz::LocalSurfaceId& local_surface_id); // Properties of the `frame` from the last `SubmitCompositorFrame()` call,
diff --git a/components/exo/shell_surface_base.cc b/components/exo/shell_surface_base.cc index f8306442..fcbe8e1 100644 --- a/components/exo/shell_surface_base.cc +++ b/components/exo/shell_surface_base.cc
@@ -4,9 +4,6 @@ #include "components/exo/shell_surface_base.h" -#include <algorithm> - -#include "ash/constants/ash_constants.h" #include "ash/display/screen_orientation_controller.h" #include "ash/frame/non_client_frame_view_ash.h" #include "ash/metrics/login_unlock_throughput_recorder.h" @@ -33,6 +30,7 @@ #include "chromeos/ui/base/window_pin_type.h" #include "chromeos/ui/base/window_properties.h" #include "chromeos/ui/frame/caption_buttons/snap_controller.h" +#include "chromeos/ui/frame/frame_utils.h" #include "chromeos/ui/frame/multitask_menu/float_controller_base.h" #include "components/app_restore/app_restore_utils.h" #include "components/app_restore/window_properties.h" @@ -135,6 +133,20 @@ return ash::NonClientFrameViewAsh::GetBoundsForClientView(); return bounds(); } + + // Overridden from views::NonClientFrameView: + void UpdateWindowRoundedCorners() override { + if (!GetFrameEnabled()) { + return; + } + + // TODO(zoraiznaeem): Get frame radius from client. + header_view_->SetHeaderCornerRadius( + chromeos::GetFrameCornerRadius(frame()->GetNativeWindow())); + + // TODO(crbug/1415486): Apply rounded corners to exo’s root surface. + } + gfx::Rect GetWindowBoundsForClientBounds( const gfx::Rect& client_bounds) const override { if (GetFrameEnabled()) {
diff --git a/components/exo/surface_tree_host.cc b/components/exo/surface_tree_host.cc index 048cc51..2bfadc6 100644 --- a/components/exo/surface_tree_host.cc +++ b/components/exo/surface_tree_host.cc
@@ -403,7 +403,8 @@ frame_callbacks_.emplace(); active_presentation_callbacks_[frame.metadata.frame_token] = PresentationCallbacks(); - layer_tree_frame_sink_holder_->SubmitCompositorFrame(std::move(frame)); + layer_tree_frame_sink_holder_->SubmitCompositorFrame(std::move(frame), + /*submit_now=*/true); } void SurfaceTreeHost::UpdateHostWindowSizeAndRootSurfaceOrigin() {
diff --git a/components/exo/wayland/BUILD.gn b/components/exo/wayland/BUILD.gn index e260e4c..4ae31de 100644 --- a/components/exo/wayland/BUILD.gn +++ b/components/exo/wayland/BUILD.gn
@@ -149,6 +149,7 @@ "//components/exo/wayland/protocol:chrome_color_management_protocol", "//components/exo/wayland/protocol:overlay_prioritizer_protocol", "//components/exo/wayland/protocol:surface_augmenter_protocol", + "//components/version_info", "//device/gamepad", "//services/device/public/mojom", "//services/device/wake_lock/power_save_blocker",
diff --git a/components/exo/wayland/DEPS b/components/exo/wayland/DEPS index a9de1ae..92112e6 100644 --- a/components/exo/wayland/DEPS +++ b/components/exo/wayland/DEPS
@@ -10,4 +10,7 @@ "zcr_remote_shell_impl_unittest\.cc": [ "+chromeos/ui/wm/features.h", ], + "zaura_shell\.cc": [ + "+components/version_info/version_info.h", + ], }
diff --git a/components/exo/wayland/clients/client_base.cc b/components/exo/wayland/clients/client_base.cc index c3dd8be..8aefcc6 100644 --- a/components/exo/wayland/clients/client_base.cc +++ b/components/exo/wayland/clients/client_base.cc
@@ -1175,7 +1175,9 @@ [](void* data, struct zaura_shell* zaura_shell, struct wl_surface* gained_active, struct wl_surface* lost_active) {}, [](void* data, struct zaura_shell* zaura_shell) {}, - [](void* data, struct zaura_shell* zaura_shell) {}}; + [](void* data, struct zaura_shell* zaura_shell) {}, + [](void* data, struct zaura_shell* zaura_shell, + const char* compositor_version) {}}; zaura_shell_add_listener(globals_.aura_shell.get(), &kAuraShellListener, this);
diff --git a/components/exo/wayland/protocol/aura-shell.xml b/components/exo/wayland/protocol/aura-shell.xml index 8629fea..06623fc 100644 --- a/components/exo/wayland/protocol/aura-shell.xml +++ b/components/exo/wayland/protocol/aura-shell.xml
@@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE. </copyright> - <interface name="zaura_shell" version="57"> + <interface name="zaura_shell" version="58"> <description summary="aura_shell"> The global interface exposing aura shell capabilities is used to instantiate an interface extension for a wl_surface object. @@ -159,7 +159,7 @@ </description> </request> - <!-- Version 57 additions --> + <!-- Version 57 additions --> <event name="set_overview_mode" since="57"> <description summary="entered overview mode"> Notifies client that the server has entered overview mode. Overview mode @@ -172,6 +172,14 @@ </description> </event> + <!-- Version 58 additions --> + <event name="compositor_version" since="58"> + <description summary="sends the server version"> + Sends the Exo compositor version information. + </description> + <arg name="version_label" type="string" summary="version string label"/> + </event> + </interface> <interface name="zaura_surface" version="51">
diff --git a/components/exo/wayland/zaura_shell.cc b/components/exo/wayland/zaura_shell.cc index 1303ff4..9560882 100644 --- a/components/exo/wayland/zaura_shell.cc +++ b/components/exo/wayland/zaura_shell.cc
@@ -44,6 +44,7 @@ #include "components/exo/wayland/wayland_display_observer.h" #include "components/exo/wayland/wl_output.h" #include "components/exo/wayland/xdg_shell.h" +#include "components/version_info/version_info.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/env.h" #include "ui/aura/window_occlusion_tracker.h" @@ -1174,6 +1175,12 @@ zaura_shell_send_layout_mode(aura_shell_resource_, layout_mode); } if (wl_resource_get_version(aura_shell_resource_) >= + ZAURA_SHELL_COMPOSITOR_VERSION_SINCE_VERSION) { + const base::StringPiece ash_version = version_info::GetVersionNumber(); + zaura_shell_send_compositor_version(aura_shell_resource_, + ash_version.data()); + } + if (wl_resource_get_version(aura_shell_resource_) >= ZAURA_SHELL_BUG_FIX_SINCE_VERSION) { for (uint32_t bug_id : kFixedBugIds) { zaura_shell_send_bug_fix(aura_shell_resource_, bug_id);
diff --git a/components/exo/wayland/zaura_shell.h b/components/exo/wayland/zaura_shell.h index 2c90637..8299819c 100644 --- a/components/exo/wayland/zaura_shell.h +++ b/components/exo/wayland/zaura_shell.h
@@ -33,7 +33,7 @@ namespace wayland { class SerialTracker; -constexpr uint32_t kZAuraShellVersion = 57; +constexpr uint32_t kZAuraShellVersion = 58; // Adds bindings to the Aura Shell. Normally this implies Ash on ChromeOS // builds. On non-ChromeOS builds the protocol provides access to Aura windowing
diff --git a/components/memory_pressure/system_memory_pressure_evaluator_mac.h b/components/memory_pressure/system_memory_pressure_evaluator_mac.h index 47bc154..17dd495 100644 --- a/components/memory_pressure/system_memory_pressure_evaluator_mac.h +++ b/components/memory_pressure/system_memory_pressure_evaluator_mac.h
@@ -8,8 +8,8 @@ #include <CoreFoundation/CFDate.h> #include <dispatch/dispatch.h> +#include "base/apple/scoped_dispatch_object.h" #include "base/mac/scoped_cftyperef.h" -#include "base/mac/scoped_dispatch_object.h" #include "base/message_loop/message_pump_apple.h" #include "base/sequence_checker.h" #include "base/timer/timer.h"
diff --git a/components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.cc b/components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.cc index 5b1bdef..5bcc3206 100644 --- a/components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.cc +++ b/components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.cc
@@ -12,7 +12,7 @@ #include <mach/mach.h> #include <mach/message.h> -#include "base/mac/mach_logging.h" +#include "base/apple/mach_logging.h" #include "mojo/public/cpp/platform/platform_channel.h" #include "mojo/public/cpp/platform/platform_channel_endpoint.h" #endif
diff --git a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.cc b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.cc index 9897e0b..da6e6e5 100644 --- a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.cc +++ b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.cc
@@ -10,11 +10,11 @@ #include <memory> #include "base/apple/dispatch_source_mach.h" +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" #include "base/functional/bind.h" #include "base/functional/callback_forward.h" -#include "base/mac/mach_logging.h" #include "base/mac/scoped_mach_msg_destroy.h" -#include "base/mac/scoped_mach_port.h" #include "base/sequence_checker.h" #include "base/task/sequenced_task_runner.h" #include "base/threading/sequence_bound.h" @@ -93,7 +93,7 @@ info->audit_token = request.trailer.msgh_audit; mojo::PlatformChannelEndpoint remote_endpoint(mojo::PlatformHandle( - base::mac::ScopedMachSendRight(request.header.msgh_remote_port))); + base::apple::ScopedMachSendRight(request.header.msgh_remote_port))); if (!remote_endpoint.is_valid()) { LOG(ERROR) << "Endpoint is invalid."; return;
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc index bde8454..e045249f 100644 --- a/components/omnibox/browser/autocomplete_controller.cc +++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -1807,18 +1807,6 @@ match_itr->RecordAdditionalInfo("ml_legacy_relevance", match_itr->relevance); match_itr->relevance = relevance_heap.top(); - - // Fuzzy matches use the scoring signals for history and bookmark - // providers with the "corrected" input. This leads to an artificially - // high confidence from the model. Correct for this by re-applying the - // penalty from the history fuzzy provider. - if (match_itr->provider && match_itr->provider->type() == - AutocompleteProvider::TYPE_HISTORY_FUZZY) { - match_itr->RecordAdditionalInfo("ml_relevance_before_penalty", - match_itr->relevance); - HistoryFuzzyProvider::ApplyRelevancePenalty( - *match_itr, match_itr->fuzzy_match_penalty); - } } relevance_heap.pop(); prediction_and_match_itr_heap.pop();
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc index f847a22..12d6332 100644 --- a/components/omnibox/browser/autocomplete_match.cc +++ b/components/omnibox/browser/autocomplete_match.cc
@@ -257,7 +257,6 @@ : provider(match.provider), relevance(match.relevance), typed_count(match.typed_count), - fuzzy_match_penalty(match.fuzzy_match_penalty), deletable(match.deletable), fill_into_edit(match.fill_into_edit), additional_text(match.additional_text), @@ -318,7 +317,6 @@ provider = std::move(match.provider); relevance = std::move(match.relevance); typed_count = std::move(match.typed_count); - fuzzy_match_penalty = std::move(match.fuzzy_match_penalty); deletable = std::move(match.deletable); fill_into_edit = std::move(match.fill_into_edit); additional_text = std::move(match.additional_text); @@ -388,7 +386,6 @@ provider = match.provider; relevance = match.relevance; typed_count = match.typed_count; - fuzzy_match_penalty = match.fuzzy_match_penalty; deletable = match.deletable; fill_into_edit = match.fill_into_edit; additional_text = match.additional_text;
diff --git a/components/omnibox/browser/autocomplete_match.h b/components/omnibox/browser/autocomplete_match.h index f749088..938dafc 100644 --- a/components/omnibox/browser/autocomplete_match.h +++ b/components/omnibox/browser/autocomplete_match.h
@@ -638,10 +638,6 @@ // set for matches from HistoryURL and HistoryQuickProvider. int typed_count = -1; - // The percentage deducted from the relevance score by the history fuzzy - // provider. This is currently used to re-apply the penalty after ML scoring. - int fuzzy_match_penalty = 0; - // True if the user should be able to delete this match. bool deletable = false;
diff --git a/components/omnibox/browser/history_fuzzy_provider.cc b/components/omnibox/browser/history_fuzzy_provider.cc index 11fc6b68..3e4f773 100644 --- a/components/omnibox/browser/history_fuzzy_provider.cc +++ b/components/omnibox/browser/history_fuzzy_provider.cc
@@ -567,15 +567,6 @@ return res; } -// static -void HistoryFuzzyProvider::ApplyRelevancePenalty(AutocompleteMatch& match, - int penalty) { - DCHECK_GE(penalty, 0); - DCHECK_LE(penalty, 100); - match.relevance -= match.relevance * penalty / 100; - match.fuzzy_match_penalty = penalty; -} - HistoryFuzzyProvider::~HistoryFuzzyProvider() = default; void HistoryFuzzyProvider::DoAutocomplete() { @@ -707,7 +698,15 @@ // Apply relevance penalty; all corrections are equal and we only apply this // to the most relevant result, so edit distance isn't needed. - ApplyRelevancePenalty(match, penalty); + DCHECK_GE(penalty, 0); + DCHECK_LE(penalty, 100); + match.relevance -= match.relevance * penalty / 100; + + // Scoring signals are calculated in the history and bookmark providers using + // the corrected input. These scoring signals are inaccurate for the true + // input, so clear them to prevent the ml model assigning an + // artificially high confidence to this suggestion. + match.scoring_signals->Clear(); return 1; }
diff --git a/components/omnibox/browser/history_fuzzy_provider.h b/components/omnibox/browser/history_fuzzy_provider.h index 5e93cd4..b10793b 100644 --- a/components/omnibox/browser/history_fuzzy_provider.h +++ b/components/omnibox/browser/history_fuzzy_provider.h
@@ -207,11 +207,6 @@ // See base/trace_event/memory_usage_estimator.h for more info. size_t EstimateMemoryUsage() const override; - // Reduces a match's relevance score according to the penalty and records the - // penalty amount. `penalty` is the percentage that will be deducted from the - // match's relevance. - static void ApplyRelevancePenalty(AutocompleteMatch& match, int penalty); - private: ~HistoryFuzzyProvider() override;
diff --git a/components/page_load_metrics/browser/observers/core/largest_contentful_paint_handler.h b/components/page_load_metrics/browser/observers/core/largest_contentful_paint_handler.h index 235128d..10e9e54 100644 --- a/components/page_load_metrics/browser/observers/core/largest_contentful_paint_handler.h +++ b/components/page_load_metrics/browser/observers/core/largest_contentful_paint_handler.h
@@ -74,9 +74,16 @@ // Returns true iff this object does not represent any paint. bool Empty() const { - // size_ and time_ should both be set or both be unset. - DCHECK((size_ != 0u && time_) || (size_ == 0u && !time_)); - return !time_; + // We set timings at the renderer side only when size is >0. Therefore it + // could be either size == 0 and time is not set or size > 0 and time is + // set. Note that when time is set, it could be 0. + CHECK((size_ != 0u && time_.has_value()) || + (size_ == 0u && !time_.has_value())); + + // Returns if timing is not set because of 0 size. + // TODO(crbug.com/1473188) We should revisit if we should check timing being + // 0 too. + return (size_ == 0u && !time_.has_value()); } // Returns true iff this object does not represent any paint OR represents an
diff --git a/components/performance_manager/service_worker_context_adapter.cc b/components/performance_manager/service_worker_context_adapter.cc index e16c159..ceabdf7 100644 --- a/components/performance_manager/service_worker_context_adapter.cc +++ b/components/performance_manager/service_worker_context_adapter.cc
@@ -29,9 +29,11 @@ void Subscribe(content::RenderProcessHost* worker_process_host); void Unsubscribe(); + // content::RenderProcessHostObserver: void RenderProcessExited( content::RenderProcessHost* host, const content::ChildProcessTerminationInfo& info) override; + void InProcessRendererExiting(content::RenderProcessHost* host) override; void RenderProcessHostDestroyed(content::RenderProcessHost* host) override; private: @@ -77,12 +79,16 @@ } void ServiceWorkerContextAdapter::RunningServiceWorker:: - RenderProcessHostDestroyed(content::RenderProcessHost* host) { - // In --single-process mode, `RenderProcessExited` is never called as there is - // no render process. The worker nodes still need to be cleaned up before the - // process node. + InProcessRendererExiting(content::RenderProcessHost* host) { CHECK(content::RenderProcessHost::run_renderer_in_process()); adapter_->OnRenderProcessExited(version_id_); + + /* This object is deleted inside the above, don't touch "this". */ +} + +void ServiceWorkerContextAdapter::RunningServiceWorker:: + RenderProcessHostDestroyed(content::RenderProcessHost* host) { + NOTREACHED_NORETURN(); } // ServiceWorkerContextAdapter::RunningServiceWorker ---------------------------
diff --git a/components/plus_addresses/plus_address_service_unittest.cc b/components/plus_addresses/plus_address_service_unittest.cc index 45503a23..a9ab060 100644 --- a/components/plus_addresses/plus_address_service_unittest.cc +++ b/components/plus_addresses/plus_address_service_unittest.cc
@@ -60,60 +60,12 @@ } TEST_F(PlusAddressServiceTest, DefaultSupportsPlusAddressesState) { - // Without explicit enablement of the feature, the `SupportsPlusAddresses` - // function should return `false`. + // By default, the `SupportsPlusAddresses` function should return `false`. PlusAddressService service; EXPECT_FALSE(service.SupportsPlusAddresses( url::Origin::Create(GURL("https://test.example")))); } -TEST_F(PlusAddressServiceTest, NullIdentityManager) { - // With explicit enablement of the feature, but without an identity manager, - // the `SupportsPlusAddresses` should return `false`. - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitAndEnableFeature(plus_addresses::kFeature); - PlusAddressService service; - EXPECT_FALSE(service.SupportsPlusAddresses( - url::Origin::Create(GURL("https://test.example")))); -} - -TEST_F(PlusAddressServiceTest, NoSignedInUser) { - // With explicit enablement of the feature, but without a signed in user, the - // `SupportsPlusAddresses` function should return `false`. - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitAndEnableFeature(plus_addresses::kFeature); - signin::IdentityTestEnvironment identity_test_env; - PlusAddressService service(identity_test_env.identity_manager()); - EXPECT_FALSE(service.SupportsPlusAddresses( - url::Origin::Create(GURL("https://test.example")))); -} - -TEST_F(PlusAddressServiceTest, FullySupported) { - // With explicit enablement of the feature, and a signed in user, the - // `SupportsPlusAddresses` function should return `true`. - base::test::ScopedFeatureList scoped_feature_list; - signin::IdentityTestEnvironment identity_test_env; - identity_test_env.MakeAccountAvailable("plus@plus.plus", - {signin::ConsentLevel::kSignin}); - scoped_feature_list.InitAndEnableFeature(plus_addresses::kFeature); - PlusAddressService service(identity_test_env.identity_manager()); - EXPECT_TRUE(service.SupportsPlusAddresses( - url::Origin::Create(GURL("https://test.example")))); -} - -TEST_F(PlusAddressServiceTest, FeatureExplicitlyDisabled) { - // With explicit disabling of the feature, the `SupportsPlusAddresses` - // function should return `false`, even if there's a signed-in user. - base::test::ScopedFeatureList scoped_feature_list; - signin::IdentityTestEnvironment identity_test_env; - identity_test_env.MakeAccountAvailable("plus@plus.plus", - {signin::ConsentLevel::kSignin}); - scoped_feature_list.InitAndDisableFeature(plus_addresses::kFeature); - PlusAddressService service(identity_test_env.identity_manager()); - EXPECT_FALSE(service.SupportsPlusAddresses( - url::Origin::Create(GURL("https://test.example")))); -} - TEST_F(PlusAddressServiceTest, OfferPlusAddressCreation) { // booleans captured by the lambda to ensure the callbacks are run. // See: docs/callback.md;l=352;drc=cc277f0f9a6227eb6f9ef5ee2e5061079fac08c6. @@ -152,23 +104,8 @@ EXPECT_TRUE(second_called); } -// Tests for the label overrides. -TEST_F(PlusAddressServiceTest, DisabledFeatureLabel) { - base::test::ScopedFeatureList scoped_feature_list; - // Disabled feature? Show the default generic text. - scoped_feature_list.InitAndDisableFeature(plus_addresses::kFeature); - PlusAddressService service; - EXPECT_EQ(service.GetCreateSuggestionLabel(), u"Lorem Ipsum"); -} - -TEST_F(PlusAddressServiceTest, DefaultLabel) { - base::test::ScopedFeatureList scoped_feature_list; - // Override not set? Show the default generic text. - scoped_feature_list.InitAndEnableFeature(plus_addresses::kFeature); - PlusAddressService service; - EXPECT_EQ(service.GetCreateSuggestionLabel(), u"Lorem Ipsum"); -} - +// Tests for the label overrides. These tests are not in the enabled/disabled +// fixtures as they vary parameters. TEST_F(PlusAddressServiceTest, LabelOverrides) { base::test::ScopedFeatureList scoped_feature_list; // Setting the override should result in echoing the override back. @@ -249,4 +186,70 @@ EXPECT_FALSE(call_observed); } +class PlusAddressServiceDisabledTest : public PlusAddressServiceTest { + protected: + void SetUp() override { + scoped_feature_list_.InitAndDisableFeature(plus_addresses::kFeature); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(PlusAddressServiceDisabledTest, FeatureExplicitlyDisabled) { + // `SupportsPlusAddresses` should return `false`, even if there's a signed-in + // user. + signin::IdentityTestEnvironment identity_test_env; + identity_test_env.MakeAccountAvailable("plus@plus.plus", + {signin::ConsentLevel::kSignin}); + PlusAddressService service(identity_test_env.identity_manager()); + EXPECT_FALSE(service.SupportsPlusAddresses( + url::Origin::Create(GURL("https://test.example")))); +} + +TEST_F(PlusAddressServiceDisabledTest, DisabledFeatureLabel) { + // Disabled feature? Show the default generic text. + PlusAddressService service; + EXPECT_EQ(service.GetCreateSuggestionLabel(), u"Lorem Ipsum"); +} + +class PlusAddressServiceEnabledTest : public PlusAddressServiceTest { + private: + base::test::ScopedFeatureList scoped_feature_list_{plus_addresses::kFeature}; +}; + +TEST_F(PlusAddressServiceEnabledTest, NullIdentityManager) { + // Without an identity manager, the `SupportsPlusAddresses` should return + // `false`. + PlusAddressService service; + EXPECT_FALSE(service.SupportsPlusAddresses( + url::Origin::Create(GURL("https://test.example")))); +} + +TEST_F(PlusAddressServiceEnabledTest, NoSignedInUser) { + // Without a signed in user, the `SupportsPlusAddresses` function should + // return `false`. + signin::IdentityTestEnvironment identity_test_env; + PlusAddressService service(identity_test_env.identity_manager()); + EXPECT_FALSE(service.SupportsPlusAddresses( + url::Origin::Create(GURL("https://test.example")))); +} + +TEST_F(PlusAddressServiceEnabledTest, FullySupported) { + // With a signed in user, the `SupportsPlusAddresses` function should return + // `true`. + signin::IdentityTestEnvironment identity_test_env; + identity_test_env.MakeAccountAvailable("plus@plus.plus", + {signin::ConsentLevel::kSignin}); + PlusAddressService service(identity_test_env.identity_manager()); + EXPECT_TRUE(service.SupportsPlusAddresses( + url::Origin::Create(GURL("https://test.example")))); +} + +TEST_F(PlusAddressServiceEnabledTest, DefaultLabel) { + // Override not set? Show the default generic text. + PlusAddressService service; + EXPECT_EQ(service.GetCreateSuggestionLabel(), u"Lorem Ipsum"); +} + } // namespace plus_addresses
diff --git a/components/power_metrics/mach_time_mac.mm b/components/power_metrics/mach_time_mac.mm index a688242..f93ba48 100644 --- a/components/power_metrics/mach_time_mac.mm +++ b/components/power_metrics/mach_time_mac.mm
@@ -4,8 +4,8 @@ #include "components/power_metrics/mach_time_mac.h" +#include "base/apple/mach_logging.h" #include "base/check.h" -#include "base/mac/mach_logging.h" namespace power_metrics {
diff --git a/components/safe_browsing/content/browser/BUILD.gn b/components/safe_browsing/content/browser/BUILD.gn index 3514616d..9d81b4a 100644 --- a/components/safe_browsing/content/browser/BUILD.gn +++ b/components/safe_browsing/content/browser/BUILD.gn
@@ -240,8 +240,6 @@ "client_side_detection_service.h", "client_side_phishing_model.cc", "client_side_phishing_model.h", - "client_side_phishing_model_optimization_guide.cc", - "client_side_phishing_model_optimization_guide.h", "safe_browsing_tab_observer.cc", "safe_browsing_tab_observer.h", ]
diff --git a/components/safe_browsing/content/browser/browser_url_loader_throttle.cc b/components/safe_browsing/content/browser/browser_url_loader_throttle.cc index dd517203..7a0896c 100644 --- a/components/safe_browsing/content/browser/browser_url_loader_throttle.cc +++ b/components/safe_browsing/content/browser/browser_url_loader_throttle.cc
@@ -18,7 +18,6 @@ #include "components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.h" #include "components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h" #include "components/safe_browsing/core/browser/url_checker_delegate.h" -#include "components/safe_browsing/core/browser/utils/scheme_logger.h" #include "components/safe_browsing/core/common/features.h" #include "components/safe_browsing/core/common/safebrowsing_constants.h" #include "components/safe_browsing/core/common/utils.h" @@ -358,11 +357,9 @@ return; } - original_url_ = request->url; pending_checks_++; start_request_time_ = base::TimeTicks::Now(); is_start_request_called_ = true; - request_destination_ = request->destination; if (base::FeatureList::IsEnabled(kSafeBrowsingOnUIThread)) { sb_checker_->Start(request->headers, request->load_flags, request->destination, request->has_user_gesture, @@ -388,17 +385,6 @@ net::HttpRequestHeaders* /* modified_cors_exempt_headers */) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - // TODO(crbug.com/1410939): Below histograms are for debugging. Remove them - // afterwards. - safe_browsing::scheme_logger::LogScheme( - original_url_, - "SafeBrowsing.BrowserThrottle.RedirectedOriginalUrlScheme"); - if (original_url_.SchemeIs("chrome-extension")) { - safe_browsing::scheme_logger::LogScheme( - redirect_info->new_url, - "SafeBrowsing.BrowserThrottle.RedirectedExtensionUrlScheme"); - } - if (blocked_) { // OnCheckUrlResult() has set |blocked_| to true and called // |delegate_->CancelWithError|, but this method is called before the @@ -470,18 +456,6 @@ is_response_from_cache_ ? kFromCacheUmaSuffix : kFromNetworkUmaSuffix}), interval); - // TODO(crbug.com/1410939): Below histograms are for debugging. Remove them - // afterwards. - if (!is_response_from_cache_ && interval <= base::Milliseconds(2)) { - base::UmaHistogramEnumeration( - "SafeBrowsing.BrowserThrottle.FastRequestFromNetwork." - "RequestDestination", - request_destination_); - safe_browsing::scheme_logger::LogScheme( - original_url_, - "SafeBrowsing.BrowserThrottle.FastRequestFromNetwork.UrlScheme"); - } - if (check_completed) { LogTotalDelay2MetricsWithResponseType(is_response_from_cache_, base::TimeDelta()); @@ -497,9 +471,8 @@ deferred_ = true; defer_start_time_ = base::TimeTicks::Now(); *defer = true; - TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("safe_browsing", "Deferred", - TRACE_ID_LOCAL(this), "original_url", - original_url_.spec()); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("safe_browsing", "Deferred", + TRACE_ID_LOCAL(this)); } const char* BrowserURLLoaderThrottle::NameForLoggingWillProcessResponse() {
diff --git a/components/safe_browsing/content/browser/browser_url_loader_throttle.h b/components/safe_browsing/content/browser/browser_url_loader_throttle.h index 8235fbe..9536f29 100644 --- a/components/safe_browsing/content/browser/browser_url_loader_throttle.h +++ b/components/safe_browsing/content/browser/browser_url_loader_throttle.h
@@ -232,13 +232,10 @@ bool deferred_ = false; // Whether the response loaded is from cache. bool is_response_from_cache_ = false; - network::mojom::RequestDestination request_destination_; // The total delay caused by SafeBrowsing deferring the resource load. base::TimeDelta total_delay_; - GURL original_url_; - // Whether future safe browsing checks should be skipped. bool skip_checks_ = false;
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.cc b/components/safe_browsing/content/browser/client_side_detection_host.cc index bc1c6c1..66ffab19 100644 --- a/components/safe_browsing/content/browser/client_side_detection_host.cc +++ b/components/safe_browsing/content/browser/client_side_detection_host.cc
@@ -23,7 +23,6 @@ #include "components/prefs/pref_service.h" #include "components/safe_browsing/content/browser/client_side_detection_service.h" #include "components/safe_browsing/content/browser/client_side_phishing_model.h" -#include "components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.h" #include "components/safe_browsing/content/common/safe_browsing.mojom-shared.h" #include "components/safe_browsing/content/common/safe_browsing.mojom.h" #include "components/safe_browsing/content/common/visual_utils.h" @@ -440,8 +439,9 @@ return; } - if (base::FeatureList::IsEnabled(kClientSideDetectionKillswitch)) + if (base::FeatureList::IsEnabled(kClientSideDetectionKillswitch)) { return; + } // TODO(noelutz): move this DCHECK to WebContents and fix all the unit tests // that don't call this method on the UI thread. @@ -518,12 +518,10 @@ base::UmaHistogramEnumeration("SBClientPhishing.PhishingDetectorResult", result); if (result == mojom::PhishingDetectorResult::CLASSIFIER_NOT_READY) { - bool isModelAvailable = - base::FeatureList::IsEnabled(kClientSideDetectionModelOptimizationGuide) - ? csd_service_->IsModelAvailable() - : ClientSidePhishingModel::GetInstance()->IsEnabled(); + bool is_model_available = csd_service_->IsModelAvailable(); base::UmaHistogramBoolean( - "SBClientPhishing.BrowserReadyOnClassifierNotReady", isModelAvailable); + "SBClientPhishing.BrowserReadyOnClassifierNotReady", + is_model_available); } if (result != mojom::PhishingDetectorResult::SUCCESS) return; @@ -628,9 +626,7 @@ &token); } - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide) && - base::FeatureList::IsEnabled(kClientSideDetectionModelImageEmbedder) && + if (base::FeatureList::IsEnabled(kClientSideDetectionModelImageEmbedder) && IsEnhancedProtectionEnabled(*delegate_->GetPrefs()) && csd_service_->IsModelMetadataImageEmbeddingVersionMatching()) { content::RenderFrameHost* rfh = web_contents()->GetPrimaryMainFrame();
diff --git a/components/safe_browsing/content/browser/client_side_detection_service.cc b/components/safe_browsing/content/browser/client_side_detection_service.cc index 4d700d84..b433b82 100644 --- a/components/safe_browsing/content/browser/client_side_detection_service.cc +++ b/components/safe_browsing/content/browser/client_side_detection_service.cc
@@ -30,7 +30,6 @@ #include "components/prefs/pref_service.h" #include "components/safe_browsing/content/browser/client_side_detection_host.h" #include "components/safe_browsing/content/browser/client_side_phishing_model.h" -#include "components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.h" #include "components/safe_browsing/content/browser/web_ui/safe_browsing_ui.h" #include "components/safe_browsing/content/common/safe_browsing.mojom.h" #include "components/safe_browsing/core/common/fbs/client_model_generated.h" @@ -93,12 +92,10 @@ return; } - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide) && + if (!base::FeatureList::IsEnabled(kClientSideDetectionKillswitch) && opt_guide && background_task_runner) { - client_side_phishing_model_optimization_guide_ = - std::make_unique<ClientSidePhishingModelOptimizationGuide>( - opt_guide, background_task_runner); + client_side_phishing_model_ = std::make_unique<ClientSidePhishingModel>( + opt_guide, background_task_runner); } url_loader_factory_ = delegate_->GetSafeBrowsingURLLoaderFactory(); @@ -128,7 +125,7 @@ url_loader_factory_.reset(); delegate_.reset(); enabled_ = false; - client_side_phishing_model_optimization_guide_.reset(); + client_side_phishing_model_.reset(); } void ClientSideDetectionService::OnPrefsUpdated() { @@ -137,42 +134,30 @@ bool extended_reporting = IsEnhancedProtectionEnabled(*delegate_->GetPrefs()) || IsExtendedReportingEnabled(*delegate_->GetPrefs()); - if (enabled == enabled_ && extended_reporting_ == extended_reporting) + if (enabled == enabled_ && extended_reporting_ == extended_reporting) { return; + } enabled_ = enabled; extended_reporting_ = extended_reporting; - if (enabled_) { - if (!base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - update_model_subscription_ = - ClientSidePhishingModel::GetInstance()->RegisterCallback( - base::BindRepeating( - &ClientSideDetectionService::SendModelToRenderers, - base::Unretained(this))); - } else { - if (client_side_phishing_model_optimization_guide_) { - update_model_subscription_ = - client_side_phishing_model_optimization_guide_->RegisterCallback( - base::BindRepeating( - &ClientSideDetectionService::SendModelToRenderers, - weak_factory_.GetWeakPtr())); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelImageEmbedder)) { - if (IsEnhancedProtectionEnabled(*delegate_->GetPrefs())) { - client_side_phishing_model_optimization_guide_ - ->SubscribeToImageEmbedderOptimizationGuide(); - } - } + if (enabled_ && client_side_phishing_model_) { + update_model_subscription_ = client_side_phishing_model_->RegisterCallback( + base::BindRepeating(&ClientSideDetectionService::SendModelToRenderers, + weak_factory_.GetWeakPtr())); + if (base::FeatureList::IsEnabled(kClientSideDetectionModelImageEmbedder)) { + if (IsEnhancedProtectionEnabled(*delegate_->GetPrefs())) { + client_side_phishing_model_ + ->SubscribeToImageEmbedderOptimizationGuide(); } } } else { // Invoke pending callbacks with a false verdict. for (auto& client_phishing_report : client_phishing_reports_) { ClientPhishingReportInfo* info = client_phishing_report.second.get(); - if (!info->callback.is_null()) + if (!info->callback.is_null()) { std::move(info->callback).Run(info->phishing_url, false); + } } client_phishing_reports_.clear(); cache_.clear(); @@ -212,11 +197,13 @@ base::Time::Now() - start_time); std::string data; - if (response_body) + if (response_body) { data = std::move(*response_body.get()); + } int response_code = 0; - if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) + if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) { response_code = url_loader->ResponseInfo()->headers->response_code(); + } RecordHttpResponseOrErrorCode("SBClientPhishing.NetworkResult", url_loader->NetError(), response_code); @@ -226,6 +213,12 @@ } void ClientSideDetectionService::SendModelToRenderers() { + // We will not send models to the renderer process if the feature is disabled. + // This is because the feature can be disabled via Finch in a scenario where a + // bad model is uploaded to the server. + if (base::FeatureList::IsEnabled(kClientSideDetectionKillswitch)) { + return; + } for (content::RenderProcessHost::iterator it( content::RenderProcessHost::AllHostsIterator()); !it.IsAtEnd(); it.Advance()) { @@ -240,8 +233,9 @@ DCHECK_CURRENTLY_ON(BrowserThread::UI); if (!enabled_) { - if (!callback.is_null()) + if (!callback.is_null()) { std::move(callback).Run(GURL(request->url()), false); + } return; } @@ -359,8 +353,9 @@ base::Unretained(WebUIInfoSingleton::GetInstance()), std::make_unique<ClientPhishingResponse>(response))); - if (!info->callback.is_null()) + if (!info->callback.is_null()) { std::move(info->callback).Run(info->phishing_url, is_phishing); + } } bool ClientSideDetectionService::IsInCache(const GURL& url) { @@ -443,19 +438,22 @@ phishing_report_times_.pop_front(); } - if (!delegate_ || !delegate_->GetPrefs()) + if (!delegate_ || !delegate_->GetPrefs()) { return; + } base::Value::List time_list; - for (const base::Time& report_time : phishing_report_times_) + for (const base::Time& report_time : phishing_report_times_) { time_list.Append(base::Value(report_time.ToDoubleT())); + } delegate_->GetPrefs()->SetList(prefs::kSafeBrowsingCsdPingTimestamps, std::move(time_list)); } void ClientSideDetectionService::LoadPhishingReportTimesFromPrefs() { - if (!delegate_ || !delegate_->GetPrefs()) + if (!delegate_ || !delegate_->GetPrefs()) { return; + } phishing_report_times_.clear(); for (const base::Value& timestamp : @@ -470,66 +468,42 @@ const std::string& report_url) { GURL url(report_url); std::string api_key = google_apis::GetAPIKey(); - if (!api_key.empty()) + if (!api_key.empty()) { url = url.Resolve("?key=" + base::EscapeQueryParamValue(api_key, true)); + } return url; } const std::string& ClientSideDetectionService::GetModelStr() { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - return client_side_phishing_model_optimization_guide_->GetModelStr(); - } - - return ClientSidePhishingModel::GetInstance()->GetModelStr(); + return client_side_phishing_model_->GetModelStr(); } CSDModelType ClientSideDetectionService::GetModelType() { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - return client_side_phishing_model_optimization_guide_ - ? static_cast<CSDModelType>( - client_side_phishing_model_optimization_guide_ - ->GetModelType()) - : CSDModelType::kNone; - } - - return ClientSidePhishingModel::GetInstance()->GetModelType(); + return client_side_phishing_model_ + ? client_side_phishing_model_->GetModelType() + : CSDModelType::kNone; } base::ReadOnlySharedMemoryRegion ClientSideDetectionService::GetModelSharedMemoryRegion() { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - return client_side_phishing_model_optimization_guide_ - ->GetModelSharedMemoryRegion(); - } - - return ClientSidePhishingModel::GetInstance()->GetModelSharedMemoryRegion(); + return client_side_phishing_model_->GetModelSharedMemoryRegion(); } const base::File& ClientSideDetectionService::GetVisualTfLiteModel() { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - return client_side_phishing_model_optimization_guide_ - ->GetVisualTfLiteModel(); - } - - return ClientSidePhishingModel::GetInstance()->GetVisualTfLiteModel(); + return client_side_phishing_model_->GetVisualTfLiteModel(); } const base::File& ClientSideDetectionService::GetImageEmbeddingModel() { // At launch, we will only deploy the Image Embedding Model through // OptimizationGuide - return client_side_phishing_model_optimization_guide_ - ->GetImageEmbeddingModel(); + return client_side_phishing_model_->GetImageEmbeddingModel(); } bool ClientSideDetectionService:: IsModelMetadataImageEmbeddingVersionMatching() { - return client_side_phishing_model_optimization_guide_ && - client_side_phishing_model_optimization_guide_ + return client_side_phishing_model_ && + client_side_phishing_model_ ->IsModelMetadataImageEmbeddingVersionMatching(); } @@ -545,8 +519,12 @@ void ClientSideDetectionService::SetPhishingModel( content::RenderProcessHost* rph) { + if (!IsModelAvailable()) { + return; + } if (!rph->GetChannel()) return; + mojo::AssociatedRemote<mojom::PhishingModelSetter> model_setter; rph->GetChannel()->GetRemoteAssociatedInterface(&model_setter); switch (GetModelType()) { @@ -560,8 +538,6 @@ if (delegate_ && delegate_->GetPrefs() && IsEnhancedProtectionEnabled(*delegate_->GetPrefs()) && base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide) && - base::FeatureList::IsEnabled( kClientSideDetectionModelImageEmbedder)) { if (IsModelMetadataImageEmbeddingVersionMatching()) { base::UmaHistogramBoolean( @@ -585,17 +561,18 @@ const base::flat_map<std::string, TfLiteModelMetadata::Threshold>& ClientSideDetectionService::GetVisualTfLiteModelThresholds() { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - return client_side_phishing_model_optimization_guide_ - ->GetVisualTfLiteModelThresholds(); - } - return ClientSidePhishingModel::GetInstance() - ->GetVisualTfLiteModelThresholds(); + return client_side_phishing_model_->GetVisualTfLiteModelThresholds(); } void ClientSideDetectionService::ClassifyPhishingThroughThresholds( ClientPhishingRequest* verdict) { + // This is added so that client_side_detection_host_unittest.cc can pass. + // Outside of the test, this should never occur because the model should have + // been available in order to receive the verdict in the first place. + if (!IsModelAvailable()) { + return; + } + const base::flat_map<std::string, TfLiteModelMetadata::Threshold>& label_to_thresholds_map = GetVisualTfLiteModelThresholds(); @@ -605,11 +582,11 @@ base::UmaHistogramEnumeration( "SBClientPhishing.ClassifyThresholdsResult", SBClientDetectionClassifyThresholdsResult::kModelSizeMismatch); - VLOG(0) - << "Model is misconfigured. Size is mismatched. Verdict scores size is " - << static_cast<int>(verdict->tflite_model_scores().size()) - << " and model thresholds size is " - << static_cast<int>(label_to_thresholds_map.size()); + VLOG(0) << "Model is misconfigured. Size is mismatched. Verdict scores " + "size is " + << static_cast<int>(verdict->tflite_model_scores().size()) + << " and model thresholds size is " + << static_cast<int>(label_to_thresholds_map.size()); verdict->set_is_phishing(false); verdict->set_is_tflite_match(false); return; @@ -664,21 +641,18 @@ } bool ClientSideDetectionService::IsModelAvailable() { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - return client_side_phishing_model_optimization_guide_ && - client_side_phishing_model_optimization_guide_->IsEnabled(); - } else { - return ClientSidePhishingModel::GetInstance()->IsEnabled(); + if (base::FeatureList::IsEnabled(kClientSideDetectionKillswitch)) { + return false; } + + return client_side_phishing_model_ && + client_side_phishing_model_->IsEnabled(); } bool ClientSideDetectionService::IsSubscribedToImageEmbeddingModelUpdates() { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide) && - base::FeatureList::IsEnabled(kClientSideDetectionModelImageEmbedder)) { - return client_side_phishing_model_optimization_guide_ && - client_side_phishing_model_optimization_guide_ + if (base::FeatureList::IsEnabled(kClientSideDetectionModelImageEmbedder)) { + return client_side_phishing_model_ && + client_side_phishing_model_ ->IsSubscribedToImageEmbeddingModelUpdates(); } return false; @@ -688,9 +662,8 @@ void ClientSideDetectionService::SetModelAndVisualTfLiteForTesting( const base::FilePath& model, const base::FilePath& visual_tf_lite) { - client_side_phishing_model_optimization_guide_ - ->SetModelAndVisualTfLiteForTesting( // IN-TEST - model, visual_tf_lite); + client_side_phishing_model_->SetModelAndVisualTfLiteForTesting( // IN-TEST + model, visual_tf_lite); } } // namespace safe_browsing
diff --git a/components/safe_browsing/content/browser/client_side_detection_service.h b/components/safe_browsing/content/browser/client_side_detection_service.h index 6e394f3..400b15b5 100644 --- a/components/safe_browsing/content/browser/client_side_detection_service.h +++ b/components/safe_browsing/content/browser/client_side_detection_service.h
@@ -32,7 +32,6 @@ #include "components/keyed_service/core/keyed_service.h" #include "components/prefs/pref_change_registrar.h" #include "components/safe_browsing/content/browser/client_side_phishing_model.h" -#include "components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.h" #include "components/safe_browsing/core/common/proto/csd.pb.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_observer.h" @@ -186,7 +185,9 @@ // Returns a WeakPtr for this service. base::WeakPtr<ClientSideDetectionService> GetWeakPtr(); - bool IsModelAvailable(); + // Checks whether the model class has a model available or not. Virtual so + // that mock classes can override it. + virtual bool IsModelAvailable(); // For testing the model in browser test. void SetModelAndVisualTfLiteForTesting(const base::FilePath& model, @@ -302,8 +303,7 @@ base::CallbackListSubscription update_model_subscription_; - std::unique_ptr<ClientSidePhishingModelOptimizationGuide> - client_side_phishing_model_optimization_guide_; + std::unique_ptr<ClientSidePhishingModel> client_side_phishing_model_; SEQUENCE_CHECKER(sequence_checker_);
diff --git a/components/safe_browsing/content/browser/client_side_phishing_model.cc b/components/safe_browsing/content/browser/client_side_phishing_model.cc index 5985f041..2fddd706 100644 --- a/components/safe_browsing/content/browser/client_side_phishing_model.cc +++ b/components/safe_browsing/content/browser/client_side_phishing_model.cc
@@ -1,4 +1,4 @@ -// Copyright 2020 The Chromium Authors +// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,20 +9,28 @@ #include "base/command_line.h" #include "base/feature_list.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" #include "base/logging.h" #include "base/memory/read_only_shared_memory_region.h" #include "base/memory/shared_memory_mapping.h" #include "base/memory/singleton.h" #include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" #include "base/task/thread_pool.h" #include "build/build_config.h" -#include "components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.h" +#include "components/optimization_guide/core/optimization_guide_model_provider.h" +#include "components/optimization_guide/core/optimization_guide_util.h" +#include "components/optimization_guide/proto/client_side_phishing_model_metadata.pb.h" +#include "components/optimization_guide/proto/models.pb.h" #include "components/safe_browsing/core/common/fbs/client_model_generated.h" #include "components/safe_browsing/core/common/features.h" #include "components/safe_browsing/core/common/proto/client_model.pb.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "services/network/public/cpp/shared_url_loader_factory.h" +#include "third_party/abseil-cpp/absl/types/optional.h" namespace safe_browsing { @@ -43,94 +51,427 @@ base::FilePath path, base::OnceCallback<void(std::pair<std::string, base::File>)> callback) { if (path.empty()) { - VLOG(2) << "Failed to override model. Path is empty."; + VLOG(2) << "Failed to override model. Path is empty. Path is " << path; ReturnModelOverrideFailure(std::move(callback)); return; } - base::File model(path.AppendASCII("client_model.pb"), - base::File::FLAG_OPEN | base::File::FLAG_READ); - base::File tflite_model(path.AppendASCII("visual_model.tflite"), - base::File::FLAG_OPEN | base::File::FLAG_READ); - // `tflite_model` is allowed to be invalid, when testing a DOM-only model. - if (!model.IsValid()) { - VLOG(2) << "Failed to override model. Could not open: " - << path.AppendASCII("client_model.pb"); - ReturnModelOverrideFailure(std::move(callback)); - return; - } - - std::vector<char> model_data(model.GetLength()); - if (model.ReadAtCurrentPos(model_data.data(), model_data.size()) == -1) { + std::string contents; + if (!base::ReadFileToString(path.AppendASCII("client_model.pb"), &contents)) { VLOG(2) << "Failed to override model. Could not read model data."; ReturnModelOverrideFailure(std::move(callback)); return; } + base::File tflite_model(path.AppendASCII("visual_model.tflite"), + base::File::FLAG_OPEN | base::File::FLAG_READ); + // `tflite_model` is allowed to be invalid, when testing a DOM-only model. + content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), - std::make_pair(std::string(model_data.begin(), - model_data.end()), - std::move(tflite_model)))); + FROM_HERE, + base::BindOnce(std::move(callback), + std::make_pair(contents, std::move(tflite_model)))); +} + +base::File LoadImageEmbeddingModelFile(const base::FilePath& model_file_path) { + if (!base::PathExists(model_file_path)) { + VLOG(0) + << "Model path does not exist. Returning empty file. Given path is: " + << model_file_path; + return base::File(); + } + + base::File image_embedding_model_file( + model_file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); + + bool image_embedding_model_valid = image_embedding_model_file.IsValid(); + + base::UmaHistogramBoolean( + "SBClientPhishing.ModelDynamicUpdateSuccess.ImageEmbedding", + image_embedding_model_valid); + + if (!image_embedding_model_valid) { + VLOG(2) + << "Failed to receive image embedding model file. File is not valid"; + return base::File(); + } + + return image_embedding_model_file; +} + +// Load the model file at the provided file path. +std::pair<std::string, base::File> LoadModelAndVisualTfLiteFile( + const base::FilePath& model_file_path, + base::flat_set<base::FilePath> additional_files) { + if (!base::PathExists(model_file_path)) { + VLOG(0) << "Model path does not exist. Returning empty pair. Given path is " + << model_file_path; + return std::pair<std::string, base::File>(); + } + + // The only additional file we require and expect is the visual tf lite file + if (additional_files.size() != 1) { + VLOG(2) << "Did not receive just one additional file when expected. " + "Actual size: " + << additional_files.size(); + return std::pair<std::string, base::File>(); + } + + absl::optional<base::FilePath> visual_tflite_path = absl::nullopt; + + for (const base::FilePath& path : additional_files) { + // There should only be one loop after above check + DCHECK(path.IsAbsolute()); + visual_tflite_path = path; + } + + base::File model(model_file_path, + base::File::FLAG_OPEN | base::File::FLAG_READ); + base::File tf_lite(*visual_tflite_path, + base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!model.IsValid() || !tf_lite.IsValid()) { + VLOG(2) << "Failed to override the model and/or tf_lite file."; + } + + // Convert model to string + std::string model_data; + if (!base::ReadFileToString(model_file_path, &model_data)) { + VLOG(2) << "Failed to override model. Could not read model data."; + return std::pair<std::string, base::File>(); + } + + return std::make_pair(std::string(model_data.begin(), model_data.end()), + std::move(tf_lite)); +} + +// Close the provided model file. +void CloseModelFile(base::File model_file) { + if (!model_file.IsValid()) { + return; + } + model_file.Close(); } } // namespace -using base::AutoLock; - -struct ClientSidePhishingModelSingletonTrait - : public base::DefaultSingletonTraits<ClientSidePhishingModel> { - static ClientSidePhishingModel* New() { - ClientSidePhishingModel* instance = new ClientSidePhishingModel(); - return instance; - } -}; - // --- ClientSidePhishingModel methods --- -// static -ClientSidePhishingModel* ClientSidePhishingModel::GetInstance() { - return base::Singleton<ClientSidePhishingModel, - ClientSidePhishingModelSingletonTrait>::get(); +ClientSidePhishingModel::ClientSidePhishingModel( + optimization_guide::OptimizationGuideModelProvider* opt_guide, + const scoped_refptr<base::SequencedTaskRunner>& background_task_runner) + : opt_guide_(opt_guide), + background_task_runner_(background_task_runner), + beginning_time_(base::TimeTicks::Now()) { + opt_guide_->AddObserverForOptimizationTargetModel( + optimization_guide::proto::OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING, + /*model_metadata=*/absl::nullopt, this); } -ClientSidePhishingModel::ClientSidePhishingModel() { - MaybeOverrideModel(); +void ClientSidePhishingModel::OnModelUpdated( + optimization_guide::proto::OptimizationTarget optimization_target, + base::optional_ref<const optimization_guide::ModelInfo> model_info) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (optimization_target != + optimization_guide::proto::OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING && + optimization_target != + optimization_guide::proto:: + OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING_IMAGE_EMBEDDER) { + return; + } + + if (!model_info.has_value()) { + return; + } + + if (optimization_target == + optimization_guide::proto::OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING) { + background_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, + base::BindOnce(&LoadModelAndVisualTfLiteFile, + model_info->GetModelFilePath(), + model_info->GetAdditionalFiles()), + base::BindOnce( + &ClientSidePhishingModel::OnModelAndVisualTfLiteFileLoaded, + weak_ptr_factory_.GetWeakPtr(), model_info->GetModelMetadata())); + } else if (optimization_target == + optimization_guide::proto:: + OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING_IMAGE_EMBEDDER) { + background_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, + base::BindOnce(&LoadImageEmbeddingModelFile, + model_info->GetModelFilePath()), + base::BindOnce(&ClientSidePhishingModel::OnImageEmbeddingModelLoaded, + weak_ptr_factory_.GetWeakPtr(), + model_info->GetModelMetadata())); + } +} + +void ClientSidePhishingModel::SubscribeToImageEmbedderOptimizationGuide() { + if (!subscribed_to_image_embedder_ && opt_guide_) { + subscribed_to_image_embedder_ = true; + opt_guide_->AddObserverForOptimizationTargetModel( + optimization_guide::proto:: + OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING_IMAGE_EMBEDDER, + /*model_metadata=*/absl::nullopt, this); + } +} + +bool ClientSidePhishingModel::IsSubscribedToImageEmbeddingModelUpdates() { + return subscribed_to_image_embedder_; +} + +void ClientSidePhishingModel::OnModelAndVisualTfLiteFileLoaded( + absl::optional<optimization_guide::proto::Any> model_metadata, + std::pair<std::string, base::File> model_and_tflite) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (visual_tflite_model_) { + // If the visual tf lite file is already loaded, it should be closed on a + // background thread. + background_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&CloseModelFile, std::move(*visual_tflite_model_))); + } + + std::string model_str = std::move(model_and_tflite.first); + base::File visual_tflite_model = std::move(model_and_tflite.second); + + bool model_valid = false; + int model_version_field = 0; + + bool tflite_valid = visual_tflite_model.IsValid(); + if (!base::CommandLine::ForCurrentProcess()->HasSwitch( + kOverrideCsdModelFlag) && + !model_str.empty()) { + model_type_ = CSDModelType::kNone; + flatbuffers::Verifier verifier( + reinterpret_cast<const uint8_t*>(model_str.data()), model_str.length()); + model_valid = flat::VerifyClientSideModelBuffer(verifier); + if (model_valid) { + mapped_region_ = + base::ReadOnlySharedMemoryRegion::Create(model_str.length()); + if (mapped_region_.IsValid()) { + model_type_ = CSDModelType::kFlatbuffer; + model_version_field = + flat::GetClientSideModel(model_str.data())->version(); + memcpy(mapped_region_.mapping.memory(), model_str.data(), + model_str.length()); + + const flat::ClientSideModel* flatbuffer_model = + flat::GetClientSideModel(mapped_region_.mapping.memory()); + + if (!VerifyCSDFlatBufferIndicesAndFields(flatbuffer_model)) { + VLOG(0) << "Failed to verify CSD Flatbuffer indices and fields"; + } else { + if (tflite_valid) { + thresholds_.clear(); // Clear the previous model's thresholds + // before adding on the new ones + for (const flat::TfLiteModelMetadata_::Threshold* flat_threshold : + *(flatbuffer_model->tflite_metadata()->thresholds())) { + TfLiteModelMetadata::Threshold threshold; + threshold.set_label(flat_threshold->label()->str()); + threshold.set_threshold(flat_threshold->threshold()); + threshold.set_esb_threshold(flat_threshold->esb_threshold() > 0 + ? flat_threshold->esb_threshold() + : flat_threshold->threshold()); + thresholds_[flat_threshold->label()->str()] = threshold; + } + } + } + } else { + model_valid = false; + } + base::UmaHistogramBoolean("SBClientPhishing.FlatBufferMappedRegionValid", + mapped_region_.IsValid()); + } else { + VLOG(2) << "Failed to validate flatbuffer model"; + } + } + + base::UmaHistogramBoolean("SBClientPhishing.ModelDynamicUpdateSuccess", + model_valid); + + if (model_valid) { + // At time of writing, versions go up to 25. We set a max version of 100 + // to give some room. + const int kMaxVersion = 100; + base::UmaHistogramExactLinear("SBClientPhishing.ModelDynamicUpdateVersion", + model_version_field, kMaxVersion + 1); + } + + if (tflite_valid) { + visual_tflite_model_ = std::move(visual_tflite_model); + } + + if (model_valid && tflite_valid) { + base::UmaHistogramMediumTimes( + "SBClientPhishing.OptimizationGuide.ModelFetchTime", + base::TimeTicks::Now() - beginning_time_); + + absl::optional<optimization_guide::proto::ClientSidePhishingModelMetadata> + client_side_phishing_model_metadata = absl::nullopt; + + if (model_metadata.has_value()) { + client_side_phishing_model_metadata = + optimization_guide::ParsedAnyMetadata< + optimization_guide::proto::ClientSidePhishingModelMetadata>( + model_metadata.value()); + } + + if (client_side_phishing_model_metadata.has_value()) { + trigger_model_version_ = + client_side_phishing_model_metadata->image_embedding_model_version(); + } else { + VLOG(1) << "Client side phishing model metadata is missing an image " + "embedding model version value"; + } + + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&ClientSidePhishingModel::NotifyCallbacksOnUI, + weak_ptr_factory_.GetWeakPtr())); + } +} + +void ClientSidePhishingModel::OnImageEmbeddingModelLoaded( + absl::optional<optimization_guide::proto::Any> model_metadata, + base::File image_embedding_model) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + image_embedding_model_ = std::move(image_embedding_model); + + absl::optional<optimization_guide::proto::ClientSidePhishingModelMetadata> + image_embedding_model_metadata = absl::nullopt; + + if (model_metadata.has_value()) { + image_embedding_model_metadata = optimization_guide::ParsedAnyMetadata< + optimization_guide::proto::ClientSidePhishingModelMetadata>( + model_metadata.value()); + } + + if (image_embedding_model_metadata.has_value()) { + embedding_model_version_ = + image_embedding_model_metadata->image_embedding_model_version(); + } else { + VLOG(1) << "Image embedding model metadata is missing a version value"; + } + + // There is no use of the image embedding model if the visual trigger model is + // not present, so we will only send to the renderer when that is the case. + if (visual_tflite_model_ && image_embedding_model_) { + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&ClientSidePhishingModel::NotifyCallbacksOnUI, + weak_ptr_factory_.GetWeakPtr())); + } +} + +bool ClientSidePhishingModel::IsModelMetadataImageEmbeddingVersionMatching() { + return trigger_model_version_.has_value() && + embedding_model_version_.has_value() && + trigger_model_version_.value() == embedding_model_version_.value(); } ClientSidePhishingModel::~ClientSidePhishingModel() { - AutoLock lock(lock_); // DCHECK fail if the lock is held. + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + opt_guide_->RemoveObserverForOptimizationTargetModel( + optimization_guide::proto::OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING, + this); + + if (subscribed_to_image_embedder_) { + opt_guide_->RemoveObserverForOptimizationTargetModel( + optimization_guide::proto:: + OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING_IMAGE_EMBEDDER, + this); + } + + if (visual_tflite_model_) { + background_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&CloseModelFile, std::move(*visual_tflite_model_))); + } + + if (image_embedding_model_) { + background_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&CloseModelFile, std::move(*image_embedding_model_))); + } + + opt_guide_ = nullptr; } base::CallbackListSubscription ClientSidePhishingModel::RegisterCallback( base::RepeatingCallback<void()> callback) { - AutoLock lock(lock_); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return callbacks_.Add(std::move(callback)); } bool ClientSidePhishingModel::IsEnabled() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return (model_type_ == CSDModelType::kFlatbuffer && - mapped_region_.IsValid()) || - (model_type_ == CSDModelType::kProtobuf && !model_str_.empty()) || - visual_tflite_model_.IsValid(); + mapped_region_.IsValid() && visual_tflite_model_ && + visual_tflite_model_->IsValid()) || + (model_type_ == CSDModelType::kProtobuf && !model_str_.empty()); } -const std::string& ClientSidePhishingModel::GetModelStr() const { - DCHECK(model_type_ != CSDModelType::kFlatbuffer); - return model_str_; -} +// static +bool ClientSidePhishingModel::VerifyCSDFlatBufferIndicesAndFields( + const flat::ClientSideModel* model) { + const flatbuffers::Vector<flatbuffers::Offset<flat::Hash>>* hashes = + model->hashes(); + if (!hashes) { + return false; + } -const base::File& ClientSidePhishingModel::GetVisualTfLiteModel() const { - return visual_tflite_model_; -} + const flatbuffers::Vector<flatbuffers::Offset<flat::ClientSideModel_::Rule>>* + rules = model->rule(); + if (!rules) { + return false; + } + for (const flat::ClientSideModel_::Rule* rule : *model->rule()) { + if (!rule || !rule->feature()) { + return false; + } + for (int32_t feature : *rule->feature()) { + if (feature < 0 || feature >= static_cast<int32_t>(hashes->size())) { + return false; + } + } + } -CSDModelType ClientSidePhishingModel::GetModelType() const { - return model_type_; -} + const flatbuffers::Vector<int32_t>* page_terms = model->page_term(); + if (!page_terms) { + return false; + } + for (int32_t page_term_idx : *page_terms) { + if (page_term_idx < 0 || + page_term_idx >= static_cast<int32_t>(hashes->size())) { + return false; + } + } -base::ReadOnlySharedMemoryRegion -ClientSidePhishingModel::GetModelSharedMemoryRegion() const { - return mapped_region_.region.Duplicate(); + const flatbuffers::Vector<uint32_t>* page_words = model->page_word(); + if (!page_words) { + return false; + } + + const flat::TfLiteModelMetadata* metadata = model->tflite_metadata(); + if (!metadata) { + return false; + } + const flatbuffers::Vector< + flatbuffers::Offset<flat::TfLiteModelMetadata_::Threshold>>* thresholds = + metadata->thresholds(); + if (!thresholds) { + return false; + } + for (const flat::TfLiteModelMetadata_::Threshold* threshold : *thresholds) { + if (!threshold || !threshold->label()) { + return false; + } + } + + return true; } const base::flat_map<std::string, TfLiteModelMetadata::Threshold>& @@ -138,14 +479,52 @@ return thresholds_; } -void ClientSidePhishingModel::PopulateFromDynamicUpdate( +const std::string& ClientSidePhishingModel::GetModelStr() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(model_type_ != CSDModelType::kFlatbuffer); + return model_str_; +} + +const base::File& ClientSidePhishingModel::GetVisualTfLiteModel() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(visual_tflite_model_ && visual_tflite_model_->IsValid()); + return *visual_tflite_model_; +} + +const base::File& ClientSidePhishingModel::GetImageEmbeddingModel() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(image_embedding_model_ && image_embedding_model_->IsValid()); + return *image_embedding_model_; +} + +bool ClientSidePhishingModel::HasImageEmbeddingModel() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return !!image_embedding_model_; +} + +CSDModelType ClientSidePhishingModel::GetModelType() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return model_type_; +} + +base::ReadOnlySharedMemoryRegion +ClientSidePhishingModel::GetModelSharedMemoryRegion() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return mapped_region_.region.Duplicate(); +} + +void ClientSidePhishingModel::SetModelStringForTesting( const std::string& model_str, base::File visual_tflite_model) { - AutoLock lock(lock_); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); bool model_valid = false; + int model_version_field = 0; + bool tflite_valid = visual_tflite_model.IsValid(); + // TODO (andysjlim): Move to a helper function once protobuf feature is + // removed if (!base::CommandLine::ForCurrentProcess()->HasSwitch( kOverrideCsdModelFlag) && !model_str.empty()) { @@ -160,32 +539,10 @@ base::ReadOnlySharedMemoryRegion::Create(model_str.length()); if (mapped_region_.IsValid()) { model_type_ = CSDModelType::kFlatbuffer; + model_version_field = + flat::GetClientSideModel(model_str.data())->version(); memcpy(mapped_region_.mapping.memory(), model_str.data(), model_str.length()); - - const flat::ClientSideModel* flatbuffer_model_ = - flat::GetClientSideModel(mapped_region_.mapping.memory()); - - if (!ClientSidePhishingModelOptimizationGuide:: - VerifyCSDFlatBufferIndicesAndFields(flatbuffer_model_)) { - VLOG(0) << "Failed to verify CSD Flatbuffer indices and fields"; - } else { - if (tflite_valid) { - thresholds_.clear(); // Clear the previous model's thresholds - // before adding on the new ones - for (const flat::TfLiteModelMetadata_::Threshold* flat_threshold : - *(flatbuffer_model_->tflite_metadata()->thresholds())) { - TfLiteModelMetadata::Threshold threshold; - threshold.set_label(flat_threshold->label()->str()); - threshold.set_threshold(flat_threshold->threshold()); - threshold.set_esb_threshold( - flat_threshold->esb_threshold() > 0 - ? flat_threshold->esb_threshold() - : flat_threshold->threshold()); - thresholds_[flat_threshold->label()->str()] = threshold; - } - } - } } else { model_valid = false; } @@ -198,6 +555,7 @@ model_valid = model_proto.ParseFromString(model_str); if (model_valid) { model_type_ = CSDModelType::kProtobuf; + model_version_field = model_proto.version(); model_str_ = model_str; } } @@ -205,13 +563,21 @@ base::UmaHistogramBoolean("SBClientPhishing.ModelDynamicUpdateSuccess", model_valid); + if (model_valid) { + // At time of writing, versions go up to 25. We set a max version of 100 + // to give some room. + const int kMaxVersion = 100; + base::UmaHistogramExactLinear( + "SBClientPhishing.ModelDynamicUpdateVersion", model_version_field, + kMaxVersion + 1); + } + if (tflite_valid) { visual_tflite_model_ = std::move(visual_tflite_model); } } if (model_valid || tflite_valid) { - // Unretained is safe because this is a singleton. content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&ClientSidePhishingModel::NotifyCallbacksOnUI, base::Unretained(this))); @@ -219,43 +585,45 @@ } void ClientSidePhishingModel::NotifyCallbacksOnUI() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CURRENTLY_ON(content::BrowserThread::UI); callbacks_.Notify(); } void ClientSidePhishingModel::SetModelStrForTesting( const std::string& model_str) { - AutoLock lock(lock_); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); model_str_ = model_str; } void ClientSidePhishingModel::SetVisualTfLiteModelForTesting(base::File file) { - AutoLock lock(lock_); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); visual_tflite_model_ = std::move(file); } void ClientSidePhishingModel::SetModelTypeForTesting(CSDModelType model_type) { - AutoLock lock(lock_); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); model_type_ = model_type; } void ClientSidePhishingModel::ClearMappedRegionForTesting() { - AutoLock lock(lock_); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); mapped_region_.mapping = base::WritableSharedMemoryMapping(); mapped_region_.region = base::ReadOnlySharedMemoryRegion(); } void* ClientSidePhishingModel::GetFlatBufferMemoryAddressForTesting() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return mapped_region_.mapping.memory(); } void ClientSidePhishingModel::NotifyCallbacksOfUpdateForTesting() { - // base::Unretained is safe because this is a singleton. content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&ClientSidePhishingModel::NotifyCallbacksOnUI, base::Unretained(this))); } +// This function is used for testing in client_side_phishing_model_unittest void ClientSidePhishingModel::MaybeOverrideModel() { if (base::CommandLine::ForCurrentProcess()->HasSwitch( kOverrideCsdModelFlag)) { @@ -270,15 +638,16 @@ FROM_HERE, {base::MayBlock()}, base::BindOnce( &ReadOverridenModel, overriden_model_directory, - // base::Unretained is safe because this is a singleton. base::BindOnce(&ClientSidePhishingModel::OnGetOverridenModelData, base::Unretained(this), model_type))); } } +// This function is used for testing in client_side_phishing_model_unittest void ClientSidePhishingModel::OnGetOverridenModelData( CSDModelType model_type, std::pair<std::string, base::File> model_and_tflite) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); const std::string& model_data = model_and_tflite.first; base::File tflite_model = std::move(model_and_tflite.second); if (model_data.empty()) { @@ -327,12 +696,26 @@ visual_tflite_model_ = std::move(tflite_model); } - VLOG(2) << "Model overriden successfully"; + VLOG(0) << "Model overridden successfully"; - // Unretained is safe because this is a singleton. content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&ClientSidePhishingModel::NotifyCallbacksOnUI, - base::Unretained(this))); + weak_ptr_factory_.GetWeakPtr())); +} + +// For browser unit testing in client_side_detection_service_browsertest +void ClientSidePhishingModel::SetModelAndVisualTfLiteForTesting( + const base::FilePath& model_file_path, + const base::FilePath& visual_tf_lite_model_path) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + base::flat_set<base::FilePath> additional_files; + additional_files.insert(visual_tf_lite_model_path); + background_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, + base::BindOnce(&LoadModelAndVisualTfLiteFile, model_file_path, + additional_files), + base::BindOnce(&ClientSidePhishingModel::OnModelAndVisualTfLiteFileLoaded, + weak_ptr_factory_.GetWeakPtr(), absl::nullopt)); } } // namespace safe_browsing
diff --git a/components/safe_browsing/content/browser/client_side_phishing_model.h b/components/safe_browsing/content/browser/client_side_phishing_model.h index db67392..e4d144f 100644 --- a/components/safe_browsing/content/browser/client_side_phishing_model.h +++ b/components/safe_browsing/content/browser/client_side_phishing_model.h
@@ -1,4 +1,4 @@ -// Copyright 2020 The Chromium Authors +// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -12,15 +12,23 @@ #include "base/containers/flat_map.h" #include "base/files/file.h" #include "base/gtest_prod_util.h" +#include "base/memory/raw_ptr.h" #include "base/memory/read_only_shared_memory_region.h" +#include "base/memory/weak_ptr.h" +#include "base/sequence_checker.h" #include "base/synchronization/lock.h" +#include "base/thread_annotations.h" +#include "components/optimization_guide/core/optimization_target_model_observer.h" #include "components/safe_browsing/content/browser/client_side_phishing_model.h" +#include "components/safe_browsing/core/common/fbs/client_model_generated.h" #include "components/safe_browsing/core/common/proto/client_model.pb.h" #include "components/safe_browsing/core/common/proto/csd.pb.h" -namespace safe_browsing { +namespace optimization_guide { +class OptimizationGuideModelProvider; +} // namespace optimization_guide -struct ClientSidePhishingModelSingletonTrait; +namespace safe_browsing { enum class CSDModelType { kNone = 0, kProtobuf = 1, kFlatbuffer = 2 }; @@ -29,15 +37,30 @@ // The data to populate it is fetched periodically from Google to get the most // up-to-date model. We assume it is updated at most every few hours. // -// This class is not thread safe. In particular GetModelStr() returns a -// string reference, which assumes the string won't be used and updated -// at the same time. +// This class lives on UI thread and can only be called there. In particular +// GetModelStr() returns a string reference, which assumes the string won't be +// used and updated at the same time. -class ClientSidePhishingModel { +class ClientSidePhishingModel + : public optimization_guide::OptimizationTargetModelObserver { public: - virtual ~ClientSidePhishingModel(); + ClientSidePhishingModel( + optimization_guide::OptimizationGuideModelProvider* opt_guide, + const scoped_refptr<base::SequencedTaskRunner>& background_task_runner); - static ClientSidePhishingModel* GetInstance(); // Singleton + ~ClientSidePhishingModel() override; + + // optimization_guide::OptimizationTargetModelObserver implementation + void OnModelUpdated( + optimization_guide::proto::OptimizationTarget optimization_target, + base::optional_ref<const optimization_guide::ModelInfo> model_info) + override; + + // Enhanced Safe Browsing users receive an additional image embedding model to + // be attached to CSD-Phishing ping to better train the models. + void SubscribeToImageEmbedderOptimizationGuide(); + + void UnsubscribeToImageEmbedderOptimizationGuide(); // Register a callback to be notified whenever the model changes. All // notifications will occur on the UI thread. @@ -47,6 +70,9 @@ // Returns whether we currently have a model. bool IsEnabled() const; + static bool VerifyCSDFlatBufferIndicesAndFields( + const flat::ClientSideModel* model); + // Returns model type (protobuf or flatbuffer). CSDModelType GetModelType() const; @@ -56,13 +82,14 @@ // Returns the shared memory region for the flatbuffer. base::ReadOnlySharedMemoryRegion GetModelSharedMemoryRegion() const; - // Updates the internal model string, when one is received from a component - // update. - void PopulateFromDynamicUpdate(const std::string& model_str, - base::File visual_tflite_model); - const base::File& GetVisualTfLiteModel() const; + const base::File& GetImageEmbeddingModel() const; + + bool HasImageEmbeddingModel(); + + bool IsModelMetadataImageEmbeddingVersionMatching(); + // Overrides the model string for use in tests. void SetModelStrForTesting(const std::string& model_str); void SetVisualTfLiteModelForTesting(base::File file); @@ -78,46 +105,99 @@ const base::flat_map<std::string, TfLiteModelMetadata::Threshold>& GetVisualTfLiteModelThresholds() const; - // Called to check the command line and maybe override the current model. + // This function is used to override internal model for testing in + // client_side_phishing_model_unittest void MaybeOverrideModel(); + void OnModelAndVisualTfLiteFileLoaded( + absl::optional<optimization_guide::proto::Any> model_metadata, + std::pair<std::string, base::File> model_and_tflite); + + void OnImageEmbeddingModelLoaded( + absl::optional<optimization_guide::proto::Any> model_metadata, + base::File image_embedding_model_data); + + void SetModelAndVisualTfLiteForTesting( + const base::FilePath& model_file_path, + const base::FilePath& visual_tf_lite_model_path); + + // Updates the internal model string, when one is received from testing in + // client_side_phishing_model_unittest + void SetModelStringForTesting(const std::string& model_str, + base::File visual_tflite_model); + + bool IsSubscribedToImageEmbeddingModelUpdates(); + private: static const int kInitialClientModelFetchDelayMs; - ClientSidePhishingModel(); - void NotifyCallbacksOnUI(); - // Callback when the local file overriding the model has been read. + // Callback when the file overriding the model has been read in + // client_side_phishing_model_unittest void OnGetOverridenModelData( CSDModelType model_type, std::pair<std::string, base::File> model_and_tflite); - // The list of callbacks to notify when a new model is ready. Protected by - // lock_. Will always be notified on the UI thread. - base::RepeatingCallbackList<void()> callbacks_; + // The list of callbacks to notify when a new model is ready. Guarded by + // sequence_checker_. Will always be notified on the UI thread. + base::RepeatingCallbackList<void()> callbacks_ + GUARDED_BY_CONTEXT(sequence_checker_); - // Model protobuf string. Protected by lock_. - std::string model_str_; + // Model protobuf string. Guarded by sequence_checker_. + std::string model_str_ GUARDED_BY_CONTEXT(sequence_checker_); - // Visual TFLite model file. Protected by lock_. - base::File visual_tflite_model_; + // Visual TFLite model file. Guarded by sequence_checker_. + absl::optional<base::File> visual_tflite_model_ + GUARDED_BY_CONTEXT(sequence_checker_); + + // Image Embedding TfLite model file. Guarded by sequence_checker_. + absl::optional<base::File> image_embedding_model_ + GUARDED_BY_CONTEXT(sequence_checker_); // Thresholds in visual TFLite model file to be used for comparison after // visual classification base::flat_map<std::string, TfLiteModelMetadata::Threshold> thresholds_; - // Model type as inferred by feature flag. Protected by lock_. - CSDModelType model_type_ = CSDModelType::kNone; + // Model type as inferred by feature flag. Guarded by sequence_checker_. + CSDModelType model_type_ GUARDED_BY_CONTEXT(sequence_checker_) = + CSDModelType::kNone; - // MappedReadOnlyRegion where the flatbuffer has been copied to. Protected by - // lock_. - base::MappedReadOnlyRegion mapped_region_ = base::MappedReadOnlyRegion(); + // MappedReadOnlyRegion where the flatbuffer has been copied to. Guarded by + // sequence_checker_. + base::MappedReadOnlyRegion mapped_region_ + GUARDED_BY_CONTEXT(sequence_checker_) = base::MappedReadOnlyRegion(); - mutable base::Lock lock_; - - friend struct ClientSidePhishingModelSingletonTrait; FRIEND_TEST_ALL_PREFIXES(ClientSidePhishingModelTest, CanOverrideWithFlag); + + // Optimization Guide service that provides the client side detection + // model files for this service. Optimization Guide Service is a + // BrowserContextKeyedServiceFactory and should not be used after Shutdown + raw_ptr<optimization_guide::OptimizationGuideModelProvider> opt_guide_; + + // These two integer values will be set from reading the metadata specified + // under each optimization target. These two are used to match the model + // pairings properly. If the two values match, then the image embedding model + // will be sent to the renderer process along with the trigger models. + absl::optional<int> trigger_model_version_; + absl::optional<int> embedding_model_version_; + + scoped_refptr<base::SequencedTaskRunner> background_task_runner_; + + // If the users subscribe to ESB, the code will add an observer to the + // OptimizationGuide service for the image embedder model. We can choose to + // remove the observer, but it will be on the list to be removed, and not + // removed instantly. Therefore, if the user subscribes, unsubscribes, and + // re-subscribes again in very quick succession, the code will crash because + // the DCHECK fails, indicating that the observer is added already. Therefore, + // this will be a one time use flag. + bool subscribed_to_image_embedder_ = false; + + SEQUENCE_CHECKER(sequence_checker_); + + base::TimeTicks beginning_time_; + + base::WeakPtrFactory<ClientSidePhishingModel> weak_ptr_factory_{this}; }; } // namespace safe_browsing
diff --git a/components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.cc b/components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.cc deleted file mode 100644 index 3b46bc2..0000000 --- a/components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.cc +++ /dev/null
@@ -1,745 +0,0 @@ -// Copyright 2023 The Chromium Authors -// 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/browser/client_side_phishing_model_optimization_guide.h" - -#include <stdint.h> -#include <memory> - -#include "base/command_line.h" -#include "base/feature_list.h" -#include "base/files/file.h" -#include "base/files/file_path.h" -#include "base/files/file_util.h" -#include "base/logging.h" -#include "base/memory/read_only_shared_memory_region.h" -#include "base/memory/shared_memory_mapping.h" -#include "base/memory/singleton.h" -#include "base/metrics/histogram_functions.h" -#include "base/metrics/histogram_macros.h" -#include "base/task/thread_pool.h" -#include "build/build_config.h" -#include "components/optimization_guide/core/optimization_guide_model_provider.h" -#include "components/optimization_guide/core/optimization_guide_util.h" -#include "components/optimization_guide/proto/client_side_phishing_model_metadata.pb.h" -#include "components/optimization_guide/proto/models.pb.h" -#include "components/safe_browsing/core/common/fbs/client_model_generated.h" -#include "components/safe_browsing/core/common/features.h" -#include "components/safe_browsing/core/common/proto/client_model.pb.h" -#include "content/public/browser/browser_task_traits.h" -#include "content/public/browser/browser_thread.h" -#include "services/network/public/cpp/shared_url_loader_factory.h" -#include "third_party/abseil-cpp/absl/types/optional.h" - -namespace safe_browsing { - -namespace { - -// Command-line flag that can be used to override the current CSD model. Must be -// provided with an absolute path. -const char kOverrideCsdModelFlag[] = "csd-model-override-path"; - -void ReturnModelOverrideFailure( - base::OnceCallback<void(std::pair<std::string, base::File>)> callback) { - content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), - std::make_pair(std::string(), base::File()))); -} - -void ReadOverridenModel( - base::FilePath path, - base::OnceCallback<void(std::pair<std::string, base::File>)> callback) { - if (path.empty()) { - VLOG(2) << "Failed to override model. Path is empty. Path is " << path; - ReturnModelOverrideFailure(std::move(callback)); - return; - } - - std::string contents; - if (!base::ReadFileToString(path.AppendASCII("client_model.pb"), &contents)) { - VLOG(2) << "Failed to override model. Could not read model data."; - ReturnModelOverrideFailure(std::move(callback)); - return; - } - - base::File tflite_model(path.AppendASCII("visual_model.tflite"), - base::File::FLAG_OPEN | base::File::FLAG_READ); - // `tflite_model` is allowed to be invalid, when testing a DOM-only model. - - content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), - std::make_pair(contents, std::move(tflite_model)))); -} - -base::File LoadImageEmbeddingModelFile(const base::FilePath& model_file_path) { - if (!base::PathExists(model_file_path)) { - VLOG(0) - << "Model path does not exist. Returning empty file. Given path is: " - << model_file_path; - return base::File(); - } - - base::File image_embedding_model_file( - model_file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); - - bool image_embedding_model_valid = image_embedding_model_file.IsValid(); - - base::UmaHistogramBoolean( - "SBClientPhishing.ModelDynamicUpdateSuccess.ImageEmbedding", - image_embedding_model_valid); - - if (!image_embedding_model_valid) { - VLOG(2) - << "Failed to receive image embedding model file. File is not valid"; - return base::File(); - } - - return image_embedding_model_file; -} - -// Load the model file at the provided file path. -std::pair<std::string, base::File> LoadModelAndVisualTfLiteFile( - const base::FilePath& model_file_path, - base::flat_set<base::FilePath> additional_files) { - if (!base::PathExists(model_file_path)) { - VLOG(0) << "Model path does not exist. Returning empty pair. Given path is " - << model_file_path; - return std::pair<std::string, base::File>(); - } - - // The only additional file we require and expect is the visual tf lite file - if (additional_files.size() != 1) { - VLOG(2) << "Did not receive just one additional file when expected. " - "Actual size: " - << additional_files.size(); - return std::pair<std::string, base::File>(); - } - - absl::optional<base::FilePath> visual_tflite_path = absl::nullopt; - - for (const base::FilePath& path : additional_files) { - // There should only be one loop after above check - DCHECK(path.IsAbsolute()); - visual_tflite_path = path; - } - - base::File model(model_file_path, - base::File::FLAG_OPEN | base::File::FLAG_READ); - base::File tf_lite(*visual_tflite_path, - base::File::FLAG_OPEN | base::File::FLAG_READ); - if (!model.IsValid() || !tf_lite.IsValid()) { - VLOG(2) << "Failed to override the model and/or tf_lite file."; - } - - // Convert model to string - std::string model_data; - if (!base::ReadFileToString(model_file_path, &model_data)) { - VLOG(2) << "Failed to override model. Could not read model data."; - return std::pair<std::string, base::File>(); - } - - return std::make_pair(std::string(model_data.begin(), model_data.end()), - std::move(tf_lite)); -} - -// Close the provided model file. -void CloseModelFile(base::File model_file) { - if (!model_file.IsValid()) { - return; - } - model_file.Close(); -} - -} // namespace - -// --- ClientSidePhishingModelOptimizationGuide methods --- - -ClientSidePhishingModelOptimizationGuide:: - ClientSidePhishingModelOptimizationGuide( - optimization_guide::OptimizationGuideModelProvider* opt_guide, - const scoped_refptr<base::SequencedTaskRunner>& background_task_runner) - : opt_guide_(opt_guide), - background_task_runner_(background_task_runner), - beginning_time_(base::TimeTicks::Now()) { - opt_guide_->AddObserverForOptimizationTargetModel( - optimization_guide::proto::OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING, - /*model_metadata=*/absl::nullopt, this); -} - -void ClientSidePhishingModelOptimizationGuide::OnModelUpdated( - optimization_guide::proto::OptimizationTarget optimization_target, - base::optional_ref<const optimization_guide::ModelInfo> model_info) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (optimization_target != - optimization_guide::proto::OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING && - optimization_target != - optimization_guide::proto:: - OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING_IMAGE_EMBEDDER) { - return; - } - if (!model_info.has_value()) { - return; - } - - if (optimization_target == - optimization_guide::proto::OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING) { - background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&LoadModelAndVisualTfLiteFile, - model_info->GetModelFilePath(), - model_info->GetAdditionalFiles()), - base::BindOnce(&ClientSidePhishingModelOptimizationGuide:: - OnModelAndVisualTfLiteFileLoaded, - weak_ptr_factory_.GetWeakPtr(), - model_info->GetModelMetadata())); - } else if (optimization_target == - optimization_guide::proto:: - OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING_IMAGE_EMBEDDER) { - background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&LoadImageEmbeddingModelFile, - model_info->GetModelFilePath()), - base::BindOnce(&ClientSidePhishingModelOptimizationGuide:: - OnImageEmbeddingModelLoaded, - weak_ptr_factory_.GetWeakPtr(), - model_info->GetModelMetadata())); - } -} - -void ClientSidePhishingModelOptimizationGuide:: - SubscribeToImageEmbedderOptimizationGuide() { - if (!subscribed_to_image_embedder_ && opt_guide_) { - subscribed_to_image_embedder_ = true; - opt_guide_->AddObserverForOptimizationTargetModel( - optimization_guide::proto:: - OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING_IMAGE_EMBEDDER, - /*model_metadata=*/absl::nullopt, this); - } -} - -bool ClientSidePhishingModelOptimizationGuide:: - IsSubscribedToImageEmbeddingModelUpdates() { - return subscribed_to_image_embedder_; -} - -void ClientSidePhishingModelOptimizationGuide::OnModelAndVisualTfLiteFileLoaded( - absl::optional<optimization_guide::proto::Any> model_metadata, - std::pair<std::string, base::File> model_and_tflite) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - if (visual_tflite_model_) { - // If the visual tf lite file is already loaded, it should be closed on a - // background thread. - background_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(&CloseModelFile, std::move(*visual_tflite_model_))); - } - - std::string model_str = std::move(model_and_tflite.first); - base::File visual_tflite_model = std::move(model_and_tflite.second); - - bool model_valid = false; - int model_version_field = 0; - - bool tflite_valid = visual_tflite_model.IsValid(); - if (!base::CommandLine::ForCurrentProcess()->HasSwitch( - kOverrideCsdModelFlag) && - !model_str.empty()) { - model_type_ = CSDModelTypeOptimizationGuide::kNone; - flatbuffers::Verifier verifier( - reinterpret_cast<const uint8_t*>(model_str.data()), model_str.length()); - model_valid = flat::VerifyClientSideModelBuffer(verifier); - if (model_valid) { - mapped_region_ = - base::ReadOnlySharedMemoryRegion::Create(model_str.length()); - if (mapped_region_.IsValid()) { - model_type_ = CSDModelTypeOptimizationGuide::kFlatbuffer; - model_version_field = - flat::GetClientSideModel(model_str.data())->version(); - memcpy(mapped_region_.mapping.memory(), model_str.data(), - model_str.length()); - - const flat::ClientSideModel* flatbuffer_model = - flat::GetClientSideModel(mapped_region_.mapping.memory()); - - if (!VerifyCSDFlatBufferIndicesAndFields(flatbuffer_model)) { - VLOG(0) << "Failed to verify CSD Flatbuffer indices and fields"; - } else { - if (tflite_valid) { - thresholds_.clear(); // Clear the previous model's thresholds - // before adding on the new ones - for (const flat::TfLiteModelMetadata_::Threshold* flat_threshold : - *(flatbuffer_model->tflite_metadata()->thresholds())) { - TfLiteModelMetadata::Threshold threshold; - threshold.set_label(flat_threshold->label()->str()); - threshold.set_threshold(flat_threshold->threshold()); - threshold.set_esb_threshold(flat_threshold->esb_threshold() > 0 - ? flat_threshold->esb_threshold() - : flat_threshold->threshold()); - thresholds_[flat_threshold->label()->str()] = threshold; - } - } - } - } else { - model_valid = false; - } - base::UmaHistogramBoolean("SBClientPhishing.FlatBufferMappedRegionValid", - mapped_region_.IsValid()); - } else { - VLOG(2) << "Failed to validate flatbuffer model"; - } - } - - base::UmaHistogramBoolean("SBClientPhishing.ModelDynamicUpdateSuccess", - model_valid); - - if (model_valid) { - // At time of writing, versions go up to 25. We set a max version of 100 - // to give some room. - const int kMaxVersion = 100; - base::UmaHistogramExactLinear("SBClientPhishing.ModelDynamicUpdateVersion", - model_version_field, kMaxVersion + 1); - } - - if (tflite_valid) { - visual_tflite_model_ = std::move(visual_tflite_model); - } - - if (model_valid && tflite_valid) { - base::UmaHistogramMediumTimes( - "SBClientPhishing.OptimizationGuide.ModelFetchTime", - base::TimeTicks::Now() - beginning_time_); - - absl::optional<optimization_guide::proto::ClientSidePhishingModelMetadata> - client_side_phishing_model_metadata = absl::nullopt; - - if (model_metadata.has_value()) { - client_side_phishing_model_metadata = - optimization_guide::ParsedAnyMetadata< - optimization_guide::proto::ClientSidePhishingModelMetadata>( - model_metadata.value()); - } - - if (client_side_phishing_model_metadata.has_value()) { - trigger_model_version_ = - client_side_phishing_model_metadata->image_embedding_model_version(); - } else { - VLOG(1) << "Client side phishing model metadata is missing an image " - "embedding model version value"; - } - - content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce( - &ClientSidePhishingModelOptimizationGuide::NotifyCallbacksOnUI, - weak_ptr_factory_.GetWeakPtr())); - } -} - -void ClientSidePhishingModelOptimizationGuide::OnImageEmbeddingModelLoaded( - absl::optional<optimization_guide::proto::Any> model_metadata, - base::File image_embedding_model) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - image_embedding_model_ = std::move(image_embedding_model); - - absl::optional<optimization_guide::proto::ClientSidePhishingModelMetadata> - image_embedding_model_metadata = absl::nullopt; - - if (model_metadata.has_value()) { - image_embedding_model_metadata = optimization_guide::ParsedAnyMetadata< - optimization_guide::proto::ClientSidePhishingModelMetadata>( - model_metadata.value()); - } - - if (image_embedding_model_metadata.has_value()) { - embedding_model_version_ = - image_embedding_model_metadata->image_embedding_model_version(); - } else { - VLOG(1) << "Image embedding model metadata is missing a version value"; - } - - // There is no use of the image embedding model if the visual trigger model is - // not present, so we will only send to the renderer when that is the case. - if (visual_tflite_model_ && image_embedding_model_) { - content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce( - &ClientSidePhishingModelOptimizationGuide::NotifyCallbacksOnUI, - weak_ptr_factory_.GetWeakPtr())); - } -} - -bool ClientSidePhishingModelOptimizationGuide:: - IsModelMetadataImageEmbeddingVersionMatching() { - return trigger_model_version_.has_value() && - embedding_model_version_.has_value() && - trigger_model_version_.value() == embedding_model_version_.value(); -} - -ClientSidePhishingModelOptimizationGuide:: - ~ClientSidePhishingModelOptimizationGuide() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - opt_guide_->RemoveObserverForOptimizationTargetModel( - optimization_guide::proto::OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING, - this); - - if (subscribed_to_image_embedder_) { - opt_guide_->RemoveObserverForOptimizationTargetModel( - optimization_guide::proto:: - OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING_IMAGE_EMBEDDER, - this); - } - - if (visual_tflite_model_) { - background_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(&CloseModelFile, std::move(*visual_tflite_model_))); - } - - if (image_embedding_model_) { - background_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(&CloseModelFile, std::move(*image_embedding_model_))); - } - - opt_guide_ = nullptr; -} - -base::CallbackListSubscription -ClientSidePhishingModelOptimizationGuide::RegisterCallback( - base::RepeatingCallback<void()> callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return callbacks_.Add(std::move(callback)); -} - -bool ClientSidePhishingModelOptimizationGuide::IsEnabled() const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return (model_type_ == CSDModelTypeOptimizationGuide::kFlatbuffer && - mapped_region_.IsValid() && visual_tflite_model_ && - visual_tflite_model_->IsValid()) || - (model_type_ == CSDModelTypeOptimizationGuide::kProtobuf && - !model_str_.empty()); -} - -// static -bool ClientSidePhishingModelOptimizationGuide:: - VerifyCSDFlatBufferIndicesAndFields(const flat::ClientSideModel* model) { - const flatbuffers::Vector<flatbuffers::Offset<flat::Hash>>* hashes = - model->hashes(); - if (!hashes) { - return false; - } - - const flatbuffers::Vector<flatbuffers::Offset<flat::ClientSideModel_::Rule>>* - rules = model->rule(); - if (!rules) { - return false; - } - for (const flat::ClientSideModel_::Rule* rule : *model->rule()) { - if (!rule || !rule->feature()) { - return false; - } - for (int32_t feature : *rule->feature()) { - if (feature < 0 || feature >= static_cast<int32_t>(hashes->size())) { - return false; - } - } - } - - const flatbuffers::Vector<int32_t>* page_terms = model->page_term(); - if (!page_terms) { - return false; - } - for (int32_t page_term_idx : *page_terms) { - if (page_term_idx < 0 || - page_term_idx >= static_cast<int32_t>(hashes->size())) { - return false; - } - } - - const flatbuffers::Vector<uint32_t>* page_words = model->page_word(); - if (!page_words) { - return false; - } - - const flat::TfLiteModelMetadata* metadata = model->tflite_metadata(); - if (!metadata) { - return false; - } - const flatbuffers::Vector< - flatbuffers::Offset<flat::TfLiteModelMetadata_::Threshold>>* thresholds = - metadata->thresholds(); - if (!thresholds) { - return false; - } - for (const flat::TfLiteModelMetadata_::Threshold* threshold : *thresholds) { - if (!threshold || !threshold->label()) { - return false; - } - } - - return true; -} - -const base::flat_map<std::string, TfLiteModelMetadata::Threshold>& -ClientSidePhishingModelOptimizationGuide::GetVisualTfLiteModelThresholds() - const { - return thresholds_; -} - -const std::string& ClientSidePhishingModelOptimizationGuide::GetModelStr() - const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(model_type_ != CSDModelTypeOptimizationGuide::kFlatbuffer); - return model_str_; -} - -const base::File& -ClientSidePhishingModelOptimizationGuide::GetVisualTfLiteModel() const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(visual_tflite_model_ && visual_tflite_model_->IsValid()); - return *visual_tflite_model_; -} - -const base::File& -ClientSidePhishingModelOptimizationGuide::GetImageEmbeddingModel() const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(image_embedding_model_ && image_embedding_model_->IsValid()); - return *image_embedding_model_; -} - -CSDModelTypeOptimizationGuide -ClientSidePhishingModelOptimizationGuide::GetModelType() const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return model_type_; -} - -base::ReadOnlySharedMemoryRegion -ClientSidePhishingModelOptimizationGuide::GetModelSharedMemoryRegion() const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return mapped_region_.region.Duplicate(); -} - -void ClientSidePhishingModelOptimizationGuide::SetModelStringForTesting( - const std::string& model_str, - base::File visual_tflite_model) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - bool model_valid = false; - int model_version_field = 0; - - bool tflite_valid = visual_tflite_model.IsValid(); - - // TODO (andysjlim): Move to a helper function once flatbuffer feature is - // removed - if (!base::CommandLine::ForCurrentProcess()->HasSwitch( - kOverrideCsdModelFlag) && - !model_str.empty()) { - model_type_ = CSDModelTypeOptimizationGuide::kNone; - if (base::FeatureList::IsEnabled(kClientSideDetectionModelIsFlatBuffer)) { - flatbuffers::Verifier verifier( - reinterpret_cast<const uint8_t*>(model_str.data()), - model_str.length()); - model_valid = flat::VerifyClientSideModelBuffer(verifier); - if (model_valid) { - mapped_region_ = - base::ReadOnlySharedMemoryRegion::Create(model_str.length()); - if (mapped_region_.IsValid()) { - model_type_ = CSDModelTypeOptimizationGuide::kFlatbuffer; - model_version_field = - flat::GetClientSideModel(model_str.data())->version(); - memcpy(mapped_region_.mapping.memory(), model_str.data(), - model_str.length()); - } else { - model_valid = false; - } - base::UmaHistogramBoolean( - "SBClientPhishing.FlatBufferMappedRegionValid", - mapped_region_.IsValid()); - } - } else { - ClientSideModel model_proto; - model_valid = model_proto.ParseFromString(model_str); - if (model_valid) { - model_type_ = CSDModelTypeOptimizationGuide::kProtobuf; - model_version_field = model_proto.version(); - model_str_ = model_str; - } - } - - base::UmaHistogramBoolean("SBClientPhishing.ModelDynamicUpdateSuccess", - model_valid); - - if (model_valid) { - // At time of writing, versions go up to 25. We set a max version of 100 - // to give some room. - const int kMaxVersion = 100; - base::UmaHistogramExactLinear( - "SBClientPhishing.ModelDynamicUpdateVersion", model_version_field, - kMaxVersion + 1); - } - - if (tflite_valid) { - visual_tflite_model_ = std::move(visual_tflite_model); - } - } - - if (model_valid || tflite_valid) { - content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce( - &ClientSidePhishingModelOptimizationGuide::NotifyCallbacksOnUI, - base::Unretained(this))); - } -} - -void ClientSidePhishingModelOptimizationGuide::NotifyCallbacksOnUI() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - callbacks_.Notify(); -} - -void ClientSidePhishingModelOptimizationGuide::SetModelStrForTesting( - const std::string& model_str) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - model_str_ = model_str; -} - -void ClientSidePhishingModelOptimizationGuide::SetVisualTfLiteModelForTesting( - base::File file) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - visual_tflite_model_ = std::move(file); -} - -void ClientSidePhishingModelOptimizationGuide::SetModelTypeForTesting( - CSDModelTypeOptimizationGuide model_type) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - model_type_ = model_type; -} - -void ClientSidePhishingModelOptimizationGuide::ClearMappedRegionForTesting() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - mapped_region_.mapping = base::WritableSharedMemoryMapping(); - mapped_region_.region = base::ReadOnlySharedMemoryRegion(); -} - -void* ClientSidePhishingModelOptimizationGuide:: - GetFlatBufferMemoryAddressForTesting() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return mapped_region_.mapping.memory(); -} - -void ClientSidePhishingModelOptimizationGuide:: - NotifyCallbacksOfUpdateForTesting() { - content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce( - &ClientSidePhishingModelOptimizationGuide::NotifyCallbacksOnUI, - base::Unretained(this))); -} - -// This function is used for testing in client_side_phishing_model_unittest -void ClientSidePhishingModelOptimizationGuide::MaybeOverrideModel() { - if (base::CommandLine::ForCurrentProcess()->HasSwitch( - kOverrideCsdModelFlag)) { - base::FilePath overriden_model_directory = - base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( - kOverrideCsdModelFlag); - CSDModelTypeOptimizationGuide model_type = - base::FeatureList::IsEnabled(kClientSideDetectionModelIsFlatBuffer) - ? CSDModelTypeOptimizationGuide::kFlatbuffer - : CSDModelTypeOptimizationGuide::kProtobuf; - base::ThreadPool::PostTask( - FROM_HERE, {base::MayBlock()}, - base::BindOnce( - &ReadOverridenModel, overriden_model_directory, - base::BindOnce(&ClientSidePhishingModelOptimizationGuide:: - OnGetOverridenModelData, - base::Unretained(this), model_type))); - } -} - -// This function is used for testing in client_side_phishing_model_unittest -void ClientSidePhishingModelOptimizationGuide::OnGetOverridenModelData( - CSDModelTypeOptimizationGuide model_type, - std::pair<std::string, base::File> model_and_tflite) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - const std::string& model_data = model_and_tflite.first; - base::File tflite_model = std::move(model_and_tflite.second); - if (model_data.empty()) { - VLOG(2) << "Overriden model data is empty"; - return; - } - - switch (model_type) { - case CSDModelTypeOptimizationGuide::kProtobuf: { - std::unique_ptr<ClientSideModel> model = - std::make_unique<ClientSideModel>(); - if (!model->ParseFromArray(model_data.data(), model_data.size())) { - VLOG(2) << "Overriden model data is not a valid ClientSideModel proto"; - return; - } - model_type_ = model_type; - model_str_ = model_data; - break; - } - case CSDModelTypeOptimizationGuide::kFlatbuffer: { - flatbuffers::Verifier verifier( - reinterpret_cast<const uint8_t*>(model_data.data()), - model_data.length()); - if (!flat::VerifyClientSideModelBuffer(verifier)) { - VLOG(2) - << "Overriden model data is not a valid ClientSideModel flatbuffer"; - return; - } - mapped_region_ = - base::ReadOnlySharedMemoryRegion::Create(model_data.length()); - if (!mapped_region_.IsValid()) { - VLOG(2) << "Could not create shared memory region for flatbuffer"; - return; - } - memcpy(mapped_region_.mapping.memory(), model_data.data(), - model_data.length()); - model_type_ = model_type; - break; - } - case CSDModelTypeOptimizationGuide::kNone: - VLOG(2) << "Model type should have been either proto or flatbuffer"; - return; - } - - if (tflite_model.IsValid()) { - visual_tflite_model_ = std::move(tflite_model); - } - - VLOG(0) << "Model overridden successfully"; - - content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce( - &ClientSidePhishingModelOptimizationGuide::NotifyCallbacksOnUI, - weak_ptr_factory_.GetWeakPtr())); -} - -// For browser unit testing in client_side_detection_service_browsertest -void ClientSidePhishingModelOptimizationGuide:: - SetModelAndVisualTfLiteForTesting( - const base::FilePath& model_file_path, - const base::FilePath& visual_tf_lite_model_path) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - base::flat_set<base::FilePath> additional_files; - additional_files.insert(visual_tf_lite_model_path); - background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&LoadModelAndVisualTfLiteFile, model_file_path, - additional_files), - base::BindOnce(&ClientSidePhishingModelOptimizationGuide:: - OnModelAndVisualTfLiteFileLoaded, - weak_ptr_factory_.GetWeakPtr(), absl::nullopt)); -} - -} // namespace safe_browsing
diff --git a/components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.h b/components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.h deleted file mode 100644 index 554263c..0000000 --- a/components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.h +++ /dev/null
@@ -1,209 +0,0 @@ -// Copyright 2023 The Chromium Authors -// 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_BROWSER_CLIENT_SIDE_PHISHING_MODEL_OPTIMIZATION_GUIDE_H_ -#define COMPONENTS_SAFE_BROWSING_CONTENT_BROWSER_CLIENT_SIDE_PHISHING_MODEL_OPTIMIZATION_GUIDE_H_ - -#include <map> -#include <memory> - -#include "base/callback_list.h" -#include "base/containers/flat_map.h" -#include "base/files/file.h" -#include "base/gtest_prod_util.h" -#include "base/memory/raw_ptr.h" -#include "base/memory/read_only_shared_memory_region.h" -#include "base/memory/weak_ptr.h" -#include "base/sequence_checker.h" -#include "base/synchronization/lock.h" -#include "base/thread_annotations.h" -#include "components/optimization_guide/core/optimization_target_model_observer.h" -#include "components/safe_browsing/content/browser/client_side_phishing_model.h" -#include "components/safe_browsing/core/common/fbs/client_model_generated.h" -#include "components/safe_browsing/core/common/proto/client_model.pb.h" -#include "components/safe_browsing/core/common/proto/csd.pb.h" - -namespace optimization_guide { -class OptimizationGuideModelProvider; -} // namespace optimization_guide - -namespace safe_browsing { - -enum class CSDModelTypeOptimizationGuide { - kNone = 0, - kProtobuf = 1, - kFlatbuffer = 2 -}; - -// This holds the currently active client side phishing detection model. -// -// The data to populate it is fetched periodically from Google to get the most -// up-to-date model. We assume it is updated at most every few hours. -// -// This class lives on UI thread and can only be called there. In particular -// GetModelStr() returns a string reference, which assumes the string won't be -// used and updated at the same time. - -class ClientSidePhishingModelOptimizationGuide - : public optimization_guide::OptimizationTargetModelObserver { - public: - ClientSidePhishingModelOptimizationGuide( - optimization_guide::OptimizationGuideModelProvider* opt_guide, - const scoped_refptr<base::SequencedTaskRunner>& background_task_runner); - - ~ClientSidePhishingModelOptimizationGuide() override; - - // optimization_guide::OptimizationTargetModelObserver implementation - void OnModelUpdated( - optimization_guide::proto::OptimizationTarget optimization_target, - base::optional_ref<const optimization_guide::ModelInfo> model_info) - override; - - // Enhanced Safe Browsing users receive an additional image embedding model to - // be attached to CSD-Phishing ping to better train the models. - void SubscribeToImageEmbedderOptimizationGuide(); - - void UnsubscribeToImageEmbedderOptimizationGuide(); - - // Register a callback to be notified whenever the model changes. All - // notifications will occur on the UI thread. - base::CallbackListSubscription RegisterCallback( - base::RepeatingCallback<void()> callback); - - // Returns whether we currently have a model. - bool IsEnabled() const; - - static bool VerifyCSDFlatBufferIndicesAndFields( - const flat::ClientSideModel* model); - - // Returns model type (protobuf or flatbuffer). - CSDModelTypeOptimizationGuide GetModelType() const; - - // Returns the model string, as a serialized protobuf or flatbuffer. - const std::string& GetModelStr() const; - - // Returns the shared memory region for the flatbuffer. - base::ReadOnlySharedMemoryRegion GetModelSharedMemoryRegion() const; - - const base::File& GetVisualTfLiteModel() const; - - const base::File& GetImageEmbeddingModel() const; - - bool IsModelMetadataImageEmbeddingVersionMatching(); - - // Overrides the model string for use in tests. - void SetModelStrForTesting(const std::string& model_str); - void SetVisualTfLiteModelForTesting(base::File file); - // Overrides model type. - void SetModelTypeForTesting(CSDModelTypeOptimizationGuide model_type); - // Removes mapping. - void ClearMappedRegionForTesting(); - // Get flatbuffer memory address. - void* GetFlatBufferMemoryAddressForTesting(); - // Notifies all the callbacks of a change in model. - void NotifyCallbacksOfUpdateForTesting(); - - const base::flat_map<std::string, TfLiteModelMetadata::Threshold>& - GetVisualTfLiteModelThresholds() const; - - // This function is used to override internal model for testing in - // client_side_phishing_model_unittest - void MaybeOverrideModel(); - - void OnModelAndVisualTfLiteFileLoaded( - absl::optional<optimization_guide::proto::Any> model_metadata, - std::pair<std::string, base::File> model_and_tflite); - - void OnImageEmbeddingModelLoaded( - absl::optional<optimization_guide::proto::Any> model_metadata, - base::File image_embedding_model_data); - - void SetModelAndVisualTfLiteForTesting( - const base::FilePath& model_file_path, - const base::FilePath& visual_tf_lite_model_path); - - // Updates the internal model string, when one is received from testing in - // client_side_phishing_model_unittest - void SetModelStringForTesting(const std::string& model_str, - base::File visual_tflite_model); - - bool IsSubscribedToImageEmbeddingModelUpdates(); - - private: - static const int kInitialClientModelFetchDelayMs; - - void NotifyCallbacksOnUI(); - - // Callback when the file overriding the model has been read in - // client_side_phishing_model_unittest - void OnGetOverridenModelData( - CSDModelTypeOptimizationGuide model_type, - std::pair<std::string, base::File> model_and_tflite); - - // The list of callbacks to notify when a new model is ready. Guarded by - // sequence_checker_. Will always be notified on the UI thread. - base::RepeatingCallbackList<void()> callbacks_ - GUARDED_BY_CONTEXT(sequence_checker_); - - // Model protobuf string. Guarded by sequence_checker_. - std::string model_str_ GUARDED_BY_CONTEXT(sequence_checker_); - - // Visual TFLite model file. Guarded by sequence_checker_. - absl::optional<base::File> visual_tflite_model_ - GUARDED_BY_CONTEXT(sequence_checker_); - - // Image Embedding TfLite model file. Guarded by sequence_checker_. - absl::optional<base::File> image_embedding_model_ - GUARDED_BY_CONTEXT(sequence_checker_); - - // Thresholds in visual TFLite model file to be used for comparison after - // visual classification - base::flat_map<std::string, TfLiteModelMetadata::Threshold> thresholds_; - - // Model type as inferred by feature flag. Guarded by sequence_checker_. - CSDModelTypeOptimizationGuide model_type_ GUARDED_BY_CONTEXT( - sequence_checker_) = CSDModelTypeOptimizationGuide::kNone; - - // MappedReadOnlyRegion where the flatbuffer has been copied to. Guarded by - // sequence_checker_. - base::MappedReadOnlyRegion mapped_region_ - GUARDED_BY_CONTEXT(sequence_checker_) = base::MappedReadOnlyRegion(); - - FRIEND_TEST_ALL_PREFIXES(ClientSidePhishingModelOptimizationGuideTest, - CanOverrideWithFlag); - - // Optimization Guide service that provides the client side detection - // model files for this service. Optimization Guide Service is a - // BrowserContextKeyedServiceFactory and should not be used after Shutdown - raw_ptr<optimization_guide::OptimizationGuideModelProvider> opt_guide_; - - // These two integer values will be set from reading the metadata specified - // under each optimization target. These two are used to match the model - // pairings properly. If the two values match, then the image embedding model - // will be sent to the renderer process along with the trigger models. - absl::optional<int> trigger_model_version_; - absl::optional<int> embedding_model_version_; - - scoped_refptr<base::SequencedTaskRunner> background_task_runner_; - - // If the users subscribe to ESB, the code will add an observer to the - // OptimizationGuide service for the image embedder model. We can choose to - // remove the observer, but it will be on the list to be removed, and not - // removed instantly. Therefore, if the user subscribes, unsubscribes, and - // re-subscribes again in very quick succession, the code will crash because - // the DCHECK fails, indicating that the observer is added already. Therefore, - // this will be a one time use flag. - bool subscribed_to_image_embedder_ = false; - - SEQUENCE_CHECKER(sequence_checker_); - - base::TimeTicks beginning_time_; - - base::WeakPtrFactory<ClientSidePhishingModelOptimizationGuide> - weak_ptr_factory_{this}; -}; - -} // namespace safe_browsing - -#endif // COMPONENTS_SAFE_BROWSING_CONTENT_BROWSER_CLIENT_SIDE_PHISHING_MODEL_OPTIMIZATION_GUIDE_H_
diff --git a/components/safe_browsing/content/browser/client_side_phishing_model_unittest.cc b/components/safe_browsing/content/browser/client_side_phishing_model_unittest.cc index ec7901a..4d1ad8b6 100644 --- a/components/safe_browsing/content/browser/client_side_phishing_model_unittest.cc +++ b/components/safe_browsing/content/browser/client_side_phishing_model_unittest.cc
@@ -3,7 +3,6 @@ // found in the LICENSE file. #include "components/safe_browsing/content/browser/client_side_phishing_model.h" -#include "components/safe_browsing/content/browser/client_side_phishing_model_optimization_guide.h" #include <string> #include <utility> @@ -91,15 +90,11 @@ raw_ptr<optimization_guide::OptimizationTargetModelObserver> model_observer_; }; -class ClientSidePhishingModelTest - : public content::RenderViewHostTestHarness, - public testing::WithParamInterface<std::tuple<bool, bool>> { +class ClientSidePhishingModelTest : public content::RenderViewHostTestHarness, + public testing::WithParamInterface<bool> { public: ClientSidePhishingModelTest() { std::vector<base::test::FeatureRef> enabled_features = {}; - if (ShouldEnableCacao()) { - enabled_features.push_back(kClientSideDetectionModelOptimizationGuide); - } if (ShouldEnableImageEmbedder()) { enabled_features.push_back(kClientSideDetectionModelImageEmbedder); @@ -111,17 +106,13 @@ void SetUp() override { content::RenderViewHostTestHarness::SetUp(); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - model_observer_tracker_ = - std::make_unique<ClientSidePhishingModelObserverTracker>(); - scoped_refptr<base::SequencedTaskRunner> background_task_runner = - base::ThreadPool::CreateSequencedTaskRunner( - {base::MayBlock(), base::TaskPriority::BEST_EFFORT}); - client_side_phishing_model_ = - std::make_unique<ClientSidePhishingModelOptimizationGuide>( - model_observer_tracker_.get(), background_task_runner); - } + model_observer_tracker_ = + std::make_unique<ClientSidePhishingModelObserverTracker>(); + scoped_refptr<base::SequencedTaskRunner> background_task_runner = + base::ThreadPool::CreateSequencedTaskRunner( + {base::MayBlock(), base::TaskPriority::BEST_EFFORT}); + client_side_phishing_model_ = std::make_unique<ClientSidePhishingModel>( + model_observer_tracker_.get(), background_task_runner); } void TearDown() override { @@ -148,15 +139,13 @@ task_environment()->RunUntilIdle(); } - ClientSidePhishingModelOptimizationGuide* service() { + ClientSidePhishingModel* service() { return client_side_phishing_model_.get(); } base::HistogramTester& histogram_tester() { return histogram_tester_; } - bool ShouldEnableCacao() { return get<0>(GetParam()); } - - bool ShouldEnableImageEmbedder() { return get<1>(GetParam()); } + bool ShouldEnableImageEmbedder() { return GetParam(); } protected: base::test::ScopedFeatureList feature_list_; @@ -167,8 +156,7 @@ std::unique_ptr<ClientSidePhishingModelObserverTracker> model_observer_tracker_; - std::unique_ptr<ClientSidePhishingModelOptimizationGuide> - client_side_phishing_model_; + std::unique_ptr<ClientSidePhishingModel> client_side_phishing_model_; }; namespace { @@ -193,15 +181,9 @@ } // namespace -INSTANTIATE_TEST_SUITE_P(All, - ClientSidePhishingModelTest, - testing::Combine(testing::Bool(), testing::Bool())); +INSTANTIATE_TEST_SUITE_P(All, ClientSidePhishingModelTest, testing::Bool()); TEST_P(ClientSidePhishingModelTest, ValidModel) { - if (!base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - return; - } base::FilePath model_file_path; base::PathService::Get(base::DIR_SOURCE_ROOT, &model_file_path); model_file_path = model_file_path.AppendASCII("components") @@ -224,7 +206,7 @@ additional_files_path = additional_files_path.AppendASCII("visual_model_desktop.tflite"); #endif - service()->SetModelTypeForTesting(CSDModelTypeOptimizationGuide::kFlatbuffer); + service()->SetModelTypeForTesting(CSDModelType::kFlatbuffer); ValidateModel(model_file_path, {additional_files_path}); histogram_tester().ExpectUniqueSample( @@ -247,10 +229,6 @@ } TEST_P(ClientSidePhishingModelTest, InvalidModelDueToInvalidPath) { - if (!base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - return; - } base::ScopedTempDir model_dir; EXPECT_TRUE(model_dir.CreateUniqueTempDir()); base::FilePath model_file_path = @@ -276,155 +254,76 @@ histogram_tester().ExpectUniqueSample( "SBClientPhishing.ModelDynamicUpdateSuccess", false, 1); - EXPECT_FALSE(ClientSidePhishingModel::GetInstance()->IsEnabled()); + EXPECT_FALSE(service()->IsEnabled()); } TEST_P(ClientSidePhishingModelTest, NotifiesOnUpdate) { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStrForTesting(""); - service()->SetVisualTfLiteModelForTesting(base::File()); - service()->ClearMappedRegionForTesting(); - service()->SetModelTypeForTesting(CSDModelTypeOptimizationGuide::kNone); - } else { - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(""); - ClientSidePhishingModel::GetInstance()->SetVisualTfLiteModelForTesting( - base::File()); - ClientSidePhishingModel::GetInstance()->ClearMappedRegionForTesting(); - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kNone); - } + service()->SetModelStrForTesting(""); + service()->SetVisualTfLiteModelForTesting(base::File()); + service()->ClearMappedRegionForTesting(); + service()->SetModelTypeForTesting(CSDModelType::kNone); + base::test::ScopedFeatureList feature_list; feature_list.InitAndDisableFeature(kClientSideDetectionModelIsFlatBuffer); base::RunLoop run_loop; bool called = false; base::CallbackListSubscription subscription = - base::FeatureList::IsEnabled(kClientSideDetectionModelOptimizationGuide) - ? service()->RegisterCallback(base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)) - : ClientSidePhishingModel::GetInstance()->RegisterCallback( - base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)); + service()->RegisterCallback(base::BindRepeating( + [](base::RepeatingClosure quit_closure, bool* called) { + *called = true; + std::move(quit_closure).Run(); + }, + run_loop.QuitClosure(), &called)); ClientSideModel model; model.set_max_words_per_term(0); // Required field. - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStringForTesting(model.SerializeAsString(), - base::File()); - } else { - ClientSidePhishingModel::GetInstance()->PopulateFromDynamicUpdate( - model.SerializeAsString(), base::File()); - } + service()->SetModelStringForTesting(model.SerializeAsString(), base::File()); run_loop.Run(); EXPECT_TRUE(called); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - EXPECT_EQ(model.SerializeAsString(), service()->GetModelStr()); - EXPECT_TRUE(service()->IsEnabled()); - } else { - EXPECT_EQ(model.SerializeAsString(), - ClientSidePhishingModel::GetInstance()->GetModelStr()); - EXPECT_TRUE(ClientSidePhishingModel::GetInstance()->IsEnabled()); - } + EXPECT_EQ(model.SerializeAsString(), service()->GetModelStr()); + EXPECT_TRUE(service()->IsEnabled()); } TEST_P(ClientSidePhishingModelTest, RejectsInvalidProto) { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStrForTesting(""); - service()->SetVisualTfLiteModelForTesting(base::File()); - service()->ClearMappedRegionForTesting(); - service()->SetModelTypeForTesting(CSDModelTypeOptimizationGuide::kNone); - service()->SetModelStringForTesting("bad proto", base::File()); - EXPECT_FALSE(service()->IsEnabled()); - } else { - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(""); - ClientSidePhishingModel::GetInstance()->SetVisualTfLiteModelForTesting( - base::File()); - ClientSidePhishingModel::GetInstance()->ClearMappedRegionForTesting(); - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kNone); - ClientSidePhishingModel::GetInstance()->PopulateFromDynamicUpdate( - "bad proto", base::File()); - EXPECT_FALSE(ClientSidePhishingModel::GetInstance()->IsEnabled()); - } + service()->SetModelStrForTesting(""); + service()->SetVisualTfLiteModelForTesting(base::File()); + service()->ClearMappedRegionForTesting(); + service()->SetModelTypeForTesting(CSDModelType::kNone); + service()->SetModelStringForTesting("bad proto", base::File()); + EXPECT_FALSE(service()->IsEnabled()); } TEST_P(ClientSidePhishingModelTest, RejectsInvalidFlatbuffer) { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStrForTesting(""); - service()->SetVisualTfLiteModelForTesting(base::File()); - service()->ClearMappedRegionForTesting(); - service()->SetModelTypeForTesting(CSDModelTypeOptimizationGuide::kNone); - } else { - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(""); - ClientSidePhishingModel::GetInstance()->SetVisualTfLiteModelForTesting( - base::File()); - ClientSidePhishingModel::GetInstance()->ClearMappedRegionForTesting(); - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kNone); - } + service()->SetModelStrForTesting(""); + service()->SetVisualTfLiteModelForTesting(base::File()); + service()->ClearMappedRegionForTesting(); + service()->SetModelTypeForTesting(CSDModelType::kNone); base::test::ScopedFeatureList feature_list; feature_list.InitWithFeatures( /*enabled_features=*/{kClientSideDetectionModelIsFlatBuffer}, /*disabled_features=*/{}); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStringForTesting("bad flatbuffer", base::File()); - EXPECT_FALSE(service()->IsEnabled()); - } else { - ClientSidePhishingModel::GetInstance()->PopulateFromDynamicUpdate( - "bad flatbuffer", base::File()); - EXPECT_FALSE(ClientSidePhishingModel::GetInstance()->IsEnabled()); - } + service()->SetModelStringForTesting("bad flatbuffer", base::File()); + EXPECT_FALSE(service()->IsEnabled()); } TEST_P(ClientSidePhishingModelTest, NotifiesForFile) { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStrForTesting(""); - service()->SetVisualTfLiteModelForTesting(base::File()); - service()->ClearMappedRegionForTesting(); - service()->SetModelTypeForTesting(CSDModelTypeOptimizationGuide::kNone); - } else { - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(""); - ClientSidePhishingModel::GetInstance()->SetVisualTfLiteModelForTesting( - base::File()); - ClientSidePhishingModel::GetInstance()->ClearMappedRegionForTesting(); - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kNone); - } + service()->SetModelStrForTesting(""); + service()->SetVisualTfLiteModelForTesting(base::File()); + service()->ClearMappedRegionForTesting(); + service()->SetModelTypeForTesting(CSDModelType::kNone); base::RunLoop run_loop; bool called = false; base::CallbackListSubscription subscription = - base::FeatureList::IsEnabled(kClientSideDetectionModelOptimizationGuide) - ? service()->RegisterCallback(base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)) - : ClientSidePhishingModel::GetInstance()->RegisterCallback( - base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)); + service()->RegisterCallback(base::BindRepeating( + [](base::RepeatingClosure quit_closure, bool* called) { + *called = true; + std::move(quit_closure).Run(); + }, + run_loop.QuitClosure(), &called)); base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); @@ -437,93 +336,43 @@ file.WriteAtCurrentPos(file_contents.data(), file_contents.size()); const std::string model_str = CreateFlatBufferString(); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStringForTesting(model_str, std::move(file)); - } else { - ClientSidePhishingModel::GetInstance()->PopulateFromDynamicUpdate( - model_str, std::move(file)); - } + service()->SetModelStringForTesting(model_str, std::move(file)); run_loop.Run(); EXPECT_TRUE(called); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - EXPECT_TRUE(service()->IsEnabled()); - } else { - EXPECT_TRUE(ClientSidePhishingModel::GetInstance()->IsEnabled()); - } + EXPECT_TRUE(service()->IsEnabled()); } TEST_P(ClientSidePhishingModelTest, DoesNotNotifyOnBadInitialUpdate) { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStrForTesting(""); - service()->SetVisualTfLiteModelForTesting(base::File()); - service()->ClearMappedRegionForTesting(); - service()->SetModelTypeForTesting(CSDModelTypeOptimizationGuide::kNone); - } else { - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(""); - ClientSidePhishingModel::GetInstance()->SetVisualTfLiteModelForTesting( - base::File()); - ClientSidePhishingModel::GetInstance()->ClearMappedRegionForTesting(); - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kNone); - } + service()->SetModelStrForTesting(""); + service()->SetVisualTfLiteModelForTesting(base::File()); + service()->ClearMappedRegionForTesting(); + service()->SetModelTypeForTesting(CSDModelType::kNone); base::RunLoop run_loop; bool called = false; base::CallbackListSubscription subscription = - base::FeatureList::IsEnabled(kClientSideDetectionModelOptimizationGuide) - ? service()->RegisterCallback(base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)) - : ClientSidePhishingModel::GetInstance()->RegisterCallback( - base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)); + service()->RegisterCallback(base::BindRepeating( + [](base::RepeatingClosure quit_closure, bool* called) { + *called = true; + std::move(quit_closure).Run(); + }, + run_loop.QuitClosure(), &called)); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStringForTesting("", base::File()); - } else { - ClientSidePhishingModel::GetInstance()->PopulateFromDynamicUpdate( - "", base::File()); - } + service()->SetModelStringForTesting("", base::File()); run_loop.RunUntilIdle(); EXPECT_FALSE(called); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - EXPECT_FALSE(service()->IsEnabled()); - } else { - EXPECT_FALSE(ClientSidePhishingModel::GetInstance()->IsEnabled()); - } + EXPECT_FALSE(service()->IsEnabled()); } TEST_P(ClientSidePhishingModelTest, DoesNotNotifyOnBadFollowingUpdate) { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStrForTesting(""); - service()->SetVisualTfLiteModelForTesting(base::File()); - service()->ClearMappedRegionForTesting(); - service()->SetModelTypeForTesting(CSDModelTypeOptimizationGuide::kNone); - } else { - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(""); - ClientSidePhishingModel::GetInstance()->SetVisualTfLiteModelForTesting( - base::File()); - ClientSidePhishingModel::GetInstance()->ClearMappedRegionForTesting(); - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kNone); - } + service()->SetModelStrForTesting(""); + service()->SetVisualTfLiteModelForTesting(base::File()); + service()->ClearMappedRegionForTesting(); + service()->SetModelTypeForTesting(CSDModelType::kNone); base::RunLoop run_loop; @@ -539,68 +388,33 @@ file.WriteAtCurrentPos(file_contents.data(), file_contents.size()); const std::string model_str = CreateFlatBufferString(); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStringForTesting(model_str, std::move(file)); - } else { - ClientSidePhishingModel::GetInstance()->PopulateFromDynamicUpdate( - model_str, std::move(file)); - } + service()->SetModelStringForTesting(model_str, std::move(file)); run_loop.RunUntilIdle(); // Perform an invalid update. bool called = false; base::CallbackListSubscription subscription = - base::FeatureList::IsEnabled(kClientSideDetectionModelOptimizationGuide) - ? service()->RegisterCallback(base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)) - : ClientSidePhishingModel::GetInstance()->RegisterCallback( - base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)); + service()->RegisterCallback(base::BindRepeating( + [](base::RepeatingClosure quit_closure, bool* called) { + *called = true; + std::move(quit_closure).Run(); + }, + run_loop.QuitClosure(), &called)); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStringForTesting("", base::File()); - } else { - ClientSidePhishingModel::GetInstance()->PopulateFromDynamicUpdate( - "", base::File()); - } + service()->SetModelStringForTesting("", base::File()); run_loop.RunUntilIdle(); EXPECT_FALSE(called); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - EXPECT_TRUE(service()->IsEnabled()); - } else { - EXPECT_TRUE(ClientSidePhishingModel::GetInstance()->IsEnabled()); - } + EXPECT_TRUE(service()->IsEnabled()); } TEST_P(ClientSidePhishingModelTest, CanOverrideFlatBufferWithFlag) { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStrForTesting(""); - service()->SetVisualTfLiteModelForTesting(base::File()); - service()->ClearMappedRegionForTesting(); - service()->SetModelTypeForTesting(CSDModelTypeOptimizationGuide::kNone); - } else { - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(""); - ClientSidePhishingModel::GetInstance()->SetVisualTfLiteModelForTesting( - base::File()); - ClientSidePhishingModel::GetInstance()->ClearMappedRegionForTesting(); - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kNone); - } + service()->SetModelStrForTesting(""); + service()->SetVisualTfLiteModelForTesting(base::File()); + service()->ClearMappedRegionForTesting(); + service()->SetModelTypeForTesting(CSDModelType::kNone); base::test::ScopedFeatureList feature_list; feature_list.InitWithFeatures( /*enabled_features=*/{kClientSideDetectionModelIsFlatBuffer}, @@ -622,64 +436,31 @@ base::RunLoop run_loop; bool called = false; base::CallbackListSubscription subscription = - base::FeatureList::IsEnabled(kClientSideDetectionModelOptimizationGuide) - ? service()->RegisterCallback(base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)) - : ClientSidePhishingModel::GetInstance()->RegisterCallback( - base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)); + service()->RegisterCallback(base::BindRepeating( + [](base::RepeatingClosure quit_closure, bool* called) { + *called = true; + std::move(quit_closure).Run(); + }, + run_loop.QuitClosure(), &called)); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->MaybeOverrideModel(); - } else { - ClientSidePhishingModel::GetInstance()->MaybeOverrideModel(); - } + service()->MaybeOverrideModel(); run_loop.Run(); std::string model_str_from_shared_mem; - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - ASSERT_NO_FATAL_FAILURE(GetFlatBufferStringFromMappedMemory( - service()->GetModelSharedMemoryRegion(), &model_str_from_shared_mem)); - EXPECT_EQ(model_str_from_shared_mem, file_contents); - EXPECT_EQ(service()->GetModelType(), - CSDModelTypeOptimizationGuide::kFlatbuffer); - } else { - ASSERT_NO_FATAL_FAILURE(GetFlatBufferStringFromMappedMemory( - ClientSidePhishingModel::GetInstance()->GetModelSharedMemoryRegion(), - &model_str_from_shared_mem)); - EXPECT_EQ(model_str_from_shared_mem, file_contents); - EXPECT_EQ(ClientSidePhishingModel::GetInstance()->GetModelType(), - CSDModelType::kFlatbuffer); - } + ASSERT_NO_FATAL_FAILURE(GetFlatBufferStringFromMappedMemory( + service()->GetModelSharedMemoryRegion(), &model_str_from_shared_mem)); + EXPECT_EQ(model_str_from_shared_mem, file_contents); + EXPECT_EQ(service()->GetModelType(), CSDModelType::kFlatbuffer); + EXPECT_TRUE(called); } TEST_P(ClientSidePhishingModelTest, AcceptsValidFlatbufferIfFeatureEnabled) { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStrForTesting(""); - service()->SetVisualTfLiteModelForTesting(base::File()); - service()->ClearMappedRegionForTesting(); - service()->SetModelTypeForTesting(CSDModelTypeOptimizationGuide::kNone); - } else { - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(""); - ClientSidePhishingModel::GetInstance()->SetVisualTfLiteModelForTesting( - base::File()); - ClientSidePhishingModel::GetInstance()->ClearMappedRegionForTesting(); - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kNone); - } + service()->SetModelStrForTesting(""); + service()->SetVisualTfLiteModelForTesting(base::File()); + service()->ClearMappedRegionForTesting(); + service()->SetModelTypeForTesting(CSDModelType::kNone); base::test::ScopedFeatureList feature_list; feature_list.InitWithFeatures( @@ -688,20 +469,12 @@ base::RunLoop run_loop; bool called = false; base::CallbackListSubscription subscription = - base::FeatureList::IsEnabled(kClientSideDetectionModelOptimizationGuide) - ? service()->RegisterCallback(base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)) - : ClientSidePhishingModel::GetInstance()->RegisterCallback( - base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)); + service()->RegisterCallback(base::BindRepeating( + [](base::RepeatingClosure quit_closure, bool* called) { + *called = true; + std::move(quit_closure).Run(); + }, + run_loop.QuitClosure(), &called)); const std::string model_str = CreateFlatBufferString(); base::ScopedTempDir temp_dir; @@ -713,173 +486,92 @@ base::File::FLAG_WRITE); const std::string file_contents = "visual model file"; file.WriteAtCurrentPos(file_contents.data(), file_contents.size()); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { - service()->SetModelStringForTesting(model_str, std::move(file)); - } else { - ClientSidePhishingModel::GetInstance()->PopulateFromDynamicUpdate( - model_str, std::move(file)); - } + service()->SetModelStringForTesting(model_str, std::move(file)); run_loop.Run(); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { EXPECT_TRUE(service()->IsEnabled()); - } else { - EXPECT_TRUE(ClientSidePhishingModel::GetInstance()->IsEnabled()); - } - std::string model_str_from_shared_mem; - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { + + std::string model_str_from_shared_mem; ASSERT_NO_FATAL_FAILURE(GetFlatBufferStringFromMappedMemory( service()->GetModelSharedMemoryRegion(), &model_str_from_shared_mem)); EXPECT_EQ(model_str, model_str_from_shared_mem); - EXPECT_EQ(service()->GetModelType(), - CSDModelTypeOptimizationGuide::kFlatbuffer); - } else { - ASSERT_NO_FATAL_FAILURE(GetFlatBufferStringFromMappedMemory( - ClientSidePhishingModel::GetInstance()->GetModelSharedMemoryRegion(), - &model_str_from_shared_mem)); - EXPECT_EQ(model_str, model_str_from_shared_mem); - EXPECT_EQ(ClientSidePhishingModel::GetInstance()->GetModelType(), - CSDModelType::kFlatbuffer); - } - EXPECT_TRUE(called); + EXPECT_EQ(service()->GetModelType(), CSDModelType::kFlatbuffer); + + EXPECT_TRUE(called); } TEST_P(ClientSidePhishingModelTest, FlatbufferonFollowingUpdate) { - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { service()->SetModelStrForTesting(""); service()->SetVisualTfLiteModelForTesting(base::File()); service()->ClearMappedRegionForTesting(); - service()->SetModelTypeForTesting(CSDModelTypeOptimizationGuide::kNone); - } else { - ClientSidePhishingModel::GetInstance()->SetModelStrForTesting(""); - ClientSidePhishingModel::GetInstance()->SetVisualTfLiteModelForTesting( - base::File()); - ClientSidePhishingModel::GetInstance()->ClearMappedRegionForTesting(); - ClientSidePhishingModel::GetInstance()->SetModelTypeForTesting( - CSDModelType::kNone); - } - base::test::ScopedFeatureList feature_list; - feature_list.InitWithFeatures( - /*enabled_features=*/{kClientSideDetectionModelIsFlatBuffer}, - /*disabled_features=*/{}); - base::RunLoop run_loop; + service()->SetModelTypeForTesting(CSDModelType::kNone); - const std::string model_str1 = CreateFlatBufferString(); - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - const base::FilePath file_path = - temp_dir.GetPath().AppendASCII("visual_model.tflite"); - base::File file(file_path, base::File::FLAG_OPEN_ALWAYS | - base::File::FLAG_READ | - base::File::FLAG_WRITE); - const std::string file_contents = "visual model file"; - file.WriteAtCurrentPos(file_contents.data(), file_contents.size()); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { + base::test::ScopedFeatureList feature_list; + feature_list.InitWithFeatures( + /*enabled_features=*/{kClientSideDetectionModelIsFlatBuffer}, + /*disabled_features=*/{}); + base::RunLoop run_loop; + + const std::string model_str1 = CreateFlatBufferString(); + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + const base::FilePath file_path = + temp_dir.GetPath().AppendASCII("visual_model.tflite"); + base::File file(file_path, base::File::FLAG_OPEN_ALWAYS | + base::File::FLAG_READ | + base::File::FLAG_WRITE); + const std::string file_contents = "visual model file"; + file.WriteAtCurrentPos(file_contents.data(), file_contents.size()); service()->SetModelStringForTesting(model_str1, std::move(file)); - } else { - ClientSidePhishingModel::GetInstance()->PopulateFromDynamicUpdate( - model_str1, std::move(file)); - } - run_loop.RunUntilIdle(); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { + run_loop.RunUntilIdle(); EXPECT_TRUE(service()->IsEnabled()); - } else { - EXPECT_TRUE(ClientSidePhishingModel::GetInstance()->IsEnabled()); - } + std::string model_str_from_shared_mem1; - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { + ASSERT_NO_FATAL_FAILURE(GetFlatBufferStringFromMappedMemory( service()->GetModelSharedMemoryRegion(), &model_str_from_shared_mem1)); EXPECT_EQ(model_str1, model_str_from_shared_mem1); - EXPECT_EQ(service()->GetModelType(), - CSDModelTypeOptimizationGuide::kFlatbuffer); - } else { - ASSERT_NO_FATAL_FAILURE(GetFlatBufferStringFromMappedMemory( - ClientSidePhishingModel::GetInstance()->GetModelSharedMemoryRegion(), - &model_str_from_shared_mem1)); - EXPECT_EQ(model_str1, model_str_from_shared_mem1); - EXPECT_EQ(ClientSidePhishingModel::GetInstance()->GetModelType(), - CSDModelType::kFlatbuffer); - } + EXPECT_EQ(service()->GetModelType(), CSDModelType::kFlatbuffer); + // Should be able to write to memory with WritableSharedMemoryMapping field. + void* memory_addr = service()->GetFlatBufferMemoryAddressForTesting(); - // Should be able to write to memory with WritableSharedMemoryMapping field. - void* memory_addr = - base::FeatureList::IsEnabled(kClientSideDetectionModelOptimizationGuide) - ? service()->GetFlatBufferMemoryAddressForTesting() - : ClientSidePhishingModel::GetInstance() - ->GetFlatBufferMemoryAddressForTesting(); + EXPECT_EQ(memset(memory_addr, 'G', 1), memory_addr); - EXPECT_EQ(memset(memory_addr, 'G', 1), memory_addr); + bool called = false; + base::CallbackListSubscription subscription = + service()->RegisterCallback(base::BindRepeating( + [](base::RepeatingClosure quit_closure, bool* called) { + *called = true; + std::move(quit_closure).Run(); + }, + run_loop.QuitClosure(), &called)); - bool called = false; - base::CallbackListSubscription subscription = - base::FeatureList::IsEnabled(kClientSideDetectionModelOptimizationGuide) - ? service()->RegisterCallback(base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)) - : ClientSidePhishingModel::GetInstance()->RegisterCallback( - base::BindRepeating( - [](base::RepeatingClosure quit_closure, bool* called) { - *called = true; - std::move(quit_closure).Run(); - }, - run_loop.QuitClosure(), &called)); - - const std::string model_str2 = CreateFlatBufferString(); - base::ScopedTempDir temp_dir2; - ASSERT_TRUE(temp_dir2.CreateUniqueTempDir()); - const base::FilePath file_path2 = - temp_dir2.GetPath().AppendASCII("visual_model.tflite"); - base::File file2(file_path2, base::File::FLAG_OPEN_ALWAYS | - base::File::FLAG_READ | - base::File::FLAG_WRITE); - const std::string file_contents2 = "visual model file"; - file2.WriteAtCurrentPos(file_contents2.data(), file_contents2.size()); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { + const std::string model_str2 = CreateFlatBufferString(); + base::ScopedTempDir temp_dir2; + ASSERT_TRUE(temp_dir2.CreateUniqueTempDir()); + const base::FilePath file_path2 = + temp_dir2.GetPath().AppendASCII("visual_model.tflite"); + base::File file2(file_path2, base::File::FLAG_OPEN_ALWAYS | + base::File::FLAG_READ | + base::File::FLAG_WRITE); + const std::string file_contents2 = "visual model file"; + file2.WriteAtCurrentPos(file_contents2.data(), file_contents2.size()); service()->SetModelStringForTesting(model_str2, std::move(file2)); - } else { - ClientSidePhishingModel::GetInstance()->PopulateFromDynamicUpdate( - model_str2, std::move(file2)); - } - run_loop.RunUntilIdle(); - EXPECT_TRUE(called); - if (base::FeatureList::IsEnabled( - kClientSideDetectionModelOptimizationGuide)) { + run_loop.RunUntilIdle(); + EXPECT_TRUE(called); EXPECT_TRUE(service()->IsEnabled()); std::string model_str_from_shared_mem2; ASSERT_NO_FATAL_FAILURE(GetFlatBufferStringFromMappedMemory( service()->GetModelSharedMemoryRegion(), &model_str_from_shared_mem2)); EXPECT_EQ(model_str2, model_str_from_shared_mem2); - EXPECT_EQ(service()->GetModelType(), - CSDModelTypeOptimizationGuide::kFlatbuffer); - } else { - EXPECT_TRUE(ClientSidePhishingModel::GetInstance()->IsEnabled()); - std::string model_str_from_shared_mem2; - ASSERT_NO_FATAL_FAILURE(GetFlatBufferStringFromMappedMemory( - ClientSidePhishingModel::GetInstance()->GetModelSharedMemoryRegion(), - &model_str_from_shared_mem2)); - EXPECT_EQ(model_str2, model_str_from_shared_mem2); - EXPECT_EQ(ClientSidePhishingModel::GetInstance()->GetModelType(), - CSDModelType::kFlatbuffer); - } + EXPECT_EQ(service()->GetModelType(), CSDModelType::kFlatbuffer); - // Mapping should be undone automatically, even with a region copy lying - // around. - // Can remove this if flaky. - // Windows ASAN flake: crbug.com/1234652 + // Mapping should be undone automatically, even with a region copy lying + // around. + // Can remove this if flaky. + // Windows ASAN flake: crbug.com/1234652 #if !(BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER)) BASE_EXPECT_DEATH(memset(memory_addr, 'G', 1), ""); #endif
diff --git a/components/safe_browsing/core/browser/tailored_security_service/tailored_security_notification_result.h b/components/safe_browsing/core/browser/tailored_security_service/tailored_security_notification_result.h index ef5744c..eb569db 100644 --- a/components/safe_browsing/core/browser/tailored_security_service/tailored_security_notification_result.h +++ b/components/safe_browsing/core/browser/tailored_security_service/tailored_security_notification_result.h
@@ -19,9 +19,10 @@ kSafeBrowsingControlledByPolicy = 5, kNoBrowserAvailable = 6, kNoBrowserWindowAvailable = 7, - kPreferencesNotSynced = 8, + // kPreferencesNotSynced = 8, // Deprecated: now using history sync optin + kHistoryNotSynced = 9, - kMaxValue = kPreferencesNotSynced, + kMaxValue = kHistoryNotSynced, }; #endif // COMPONENTS_SAFE_BROWSING_CORE_BROWSER_TAILORED_SECURITY_SERVICE_TAILORED_SECURITY_NOTIFICATION_RESULT_H_
diff --git a/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.cc b/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.cc index f608cfe6..0f131897 100644 --- a/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.cc +++ b/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.cc
@@ -404,13 +404,13 @@ if (!base::FeatureList::IsEnabled(kTailoredSecurityIntegration)) return; - bool sync_preferences_enabled = + bool sync_history_enabled = sync_service_->GetUserSettings()->GetSelectedTypes().Has( - syncer::UserSelectableType::kPreferences); - if (!sync_preferences_enabled) { + syncer::UserSelectableType::kHistory); + if (!sync_history_enabled) { if (is_enabled) { RecordEnabledNotificationResult( - TailoredSecurityNotificationResult::kPreferencesNotSynced); + TailoredSecurityNotificationResult::kHistoryNotSynced); } return; }
diff --git a/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service_observer_util.cc b/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service_observer_util.cc index 3cbe443..8e1ab57 100644 --- a/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service_observer_util.cc +++ b/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service_observer_util.cc
@@ -28,10 +28,10 @@ return false; } - bool sync_preferences_enabled = + bool sync_history_enabled = sync_service->GetUserSettings()->GetSelectedTypes().Has( - syncer::UserSelectableType::kPreferences); - if (sync_preferences_enabled) { + syncer::UserSelectableType::kHistory); + if (sync_history_enabled) { return false; }
diff --git a/components/safe_browsing/core/common/features.cc b/components/safe_browsing/core/common/features.cc index 73067473..31e075a 100644 --- a/components/safe_browsing/core/common/features.cc +++ b/components/safe_browsing/core/common/features.cc
@@ -47,7 +47,7 @@ BASE_FEATURE(kDeepScanningUpdatedUX, "SafeBrowsingDeepScanningUpdatedUX", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kDelayedWarnings, "SafeBrowsingDelayedWarnings", @@ -326,10 +326,6 @@ "VisualFeaturesSizes", base::FEATURE_DISABLED_BY_DEFAULT); -BASE_FEATURE(kClientSideDetectionModelOptimizationGuide, - "ClientSideDetectionModelOptimizationGuide", - base::FEATURE_DISABLED_BY_DEFAULT); - BASE_FEATURE(kClientSideDetectionModelImageEmbedder, "ClientSideDetectionModelImageEmbedder", base::FEATURE_DISABLED_BY_DEFAULT); @@ -360,7 +356,6 @@ {&kAdSamplerTriggerFeature, false}, {&kAntiPhishingTelemetry, false}, {&kClientSideDetectionKillswitch, true}, - {&kClientSideDetectionModelOptimizationGuide, true}, {&kClientSideDetectionModelIsFlatBuffer, true}, {&kClientSideDetectionTypeForceRequest, true}, {&kDelayedWarnings, true},
diff --git a/components/safe_browsing/core/common/features.h b/components/safe_browsing/core/common/features.h index 38d1250..313dbbb 100644 --- a/components/safe_browsing/core/common/features.h +++ b/components/safe_browsing/core/common/features.h
@@ -25,7 +25,8 @@ // run on a large fraction of navigations, crashes due to the model are very // impactful, even if only a small fraction of users have a bad version of the // model. This Finch flag allows us to remediate long-tail component versions -// while we fix the root cause. +// while we fix the root cause. This will also halt the model distribution from +// OptimizationGuide. BASE_DECLARE_FEATURE(kClientSideDetectionKillswitch); // The client side detection model is a flatbuffer. @@ -321,10 +322,6 @@ // feature flag. std::string GetFileTypePoliciesTag(); -// Enables OptimizationGuide to deliver the client side phishing model instead -// of through component updater. -BASE_DECLARE_FEATURE(kClientSideDetectionModelOptimizationGuide); - // Enables new ESB specific threshold fields in Visual TF Lite model files BASE_DECLARE_FEATURE(kSafeBrowsingPhishingClassificationESBThreshold);
diff --git a/components/search_engines/template_url_prepopulate_data.cc b/components/search_engines/template_url_prepopulate_data.cc index fb459bc4..0d9d6082 100644 --- a/components/search_engines/template_url_prepopulate_data.cc +++ b/components/search_engines/template_url_prepopulate_data.cc
@@ -1238,6 +1238,7 @@ // Countries using the "United Kingdom" engine set. UNHANDLED_COUNTRY(B, M) // Bermuda + UNHANDLED_COUNTRY(C, Q) // Sark UNHANDLED_COUNTRY(F, K) // Falkland Islands UNHANDLED_COUNTRY(G, G) // Guernsey UNHANDLED_COUNTRY(G, I) // Gibraltar
diff --git a/components/search_engines/template_url_prepopulate_data_unittest.cc b/components/search_engines/template_url_prepopulate_data_unittest.cc index 3af2dbb..62ca4e40 100644 --- a/components/search_engines/template_url_prepopulate_data_unittest.cc +++ b/components/search_engines/template_url_prepopulate_data_unittest.cc
@@ -55,56 +55,88 @@ // Verifies the set of prepopulate data doesn't contain entries with duplicate // ids. TEST_F(TemplateURLPrepopulateDataTest, UniqueIDs) { - const int kCountryIds[] = { - 'A'<<8|'D', 'A'<<8|'E', 'A'<<8|'F', 'A'<<8|'G', 'A'<<8|'I', - 'A'<<8|'L', 'A'<<8|'M', 'A'<<8|'N', 'A'<<8|'O', 'A'<<8|'Q', - 'A'<<8|'R', 'A'<<8|'S', 'A'<<8|'T', 'A'<<8|'U', 'A'<<8|'W', - 'A'<<8|'X', 'A'<<8|'Z', 'B'<<8|'A', 'B'<<8|'B', 'B'<<8|'D', - 'B'<<8|'E', 'B'<<8|'F', 'B'<<8|'G', 'B'<<8|'H', 'B'<<8|'I', - 'B'<<8|'J', 'B'<<8|'M', 'B'<<8|'N', 'B'<<8|'O', 'B'<<8|'R', - 'B'<<8|'S', 'B'<<8|'T', 'B'<<8|'V', 'B'<<8|'W', 'B'<<8|'Y', - 'B'<<8|'Z', 'C'<<8|'A', 'C'<<8|'C', 'C'<<8|'D', 'C'<<8|'F', - 'C'<<8|'G', 'C'<<8|'H', 'C'<<8|'I', 'C'<<8|'K', 'C'<<8|'L', - 'C'<<8|'M', 'C'<<8|'N', 'C'<<8|'O', 'C'<<8|'R', 'C'<<8|'U', - 'C'<<8|'V', 'C'<<8|'X', 'C'<<8|'Y', 'C'<<8|'Z', 'D'<<8|'E', - 'D'<<8|'J', 'D'<<8|'K', 'D'<<8|'M', 'D'<<8|'O', 'D'<<8|'Z', - 'E'<<8|'C', 'E'<<8|'E', 'E'<<8|'G', 'E'<<8|'R', 'E'<<8|'S', - 'E'<<8|'T', 'F'<<8|'I', 'F'<<8|'J', 'F'<<8|'K', 'F'<<8|'M', - 'F'<<8|'O', 'F'<<8|'R', 'G'<<8|'A', 'G'<<8|'B', 'G'<<8|'D', - 'G'<<8|'E', 'G'<<8|'F', 'G'<<8|'G', 'G'<<8|'H', 'G'<<8|'I', - 'G'<<8|'L', 'G'<<8|'M', 'G'<<8|'N', 'G'<<8|'P', 'G'<<8|'Q', - 'G'<<8|'R', 'G'<<8|'S', 'G'<<8|'T', 'G'<<8|'U', 'G'<<8|'W', - 'G'<<8|'Y', 'H'<<8|'K', 'H'<<8|'M', 'H'<<8|'N', 'H'<<8|'R', - 'H'<<8|'T', 'H'<<8|'U', 'I'<<8|'D', 'I'<<8|'E', 'I'<<8|'L', - 'I'<<8|'M', 'I'<<8|'N', 'I'<<8|'O', 'I'<<8|'P', 'I'<<8|'Q', - 'I'<<8|'R', 'I'<<8|'S', 'I'<<8|'T', 'J'<<8|'E', 'J'<<8|'M', - 'J'<<8|'O', 'J'<<8|'P', 'K'<<8|'E', 'K'<<8|'G', 'K'<<8|'H', - 'K'<<8|'I', 'K'<<8|'M', 'K'<<8|'N', 'K'<<8|'P', 'K'<<8|'R', - 'K'<<8|'W', 'K'<<8|'Y', 'K'<<8|'Z', 'L'<<8|'A', 'L'<<8|'B', - 'L'<<8|'C', 'L'<<8|'I', 'L'<<8|'K', 'L'<<8|'R', 'L'<<8|'S', - 'L'<<8|'T', 'L'<<8|'U', 'L'<<8|'V', 'L'<<8|'Y', 'M'<<8|'A', - 'M'<<8|'C', 'M'<<8|'D', 'M'<<8|'E', 'M'<<8|'G', 'M'<<8|'H', - 'M'<<8|'K', 'M'<<8|'L', 'M'<<8|'M', 'M'<<8|'N', 'M'<<8|'O', - 'M'<<8|'P', 'M'<<8|'Q', 'M'<<8|'R', 'M'<<8|'S', 'M'<<8|'T', - 'M'<<8|'U', 'M'<<8|'V', 'M'<<8|'W', 'M'<<8|'X', 'M'<<8|'Y', - 'M'<<8|'Z', 'N'<<8|'A', 'N'<<8|'C', 'N'<<8|'E', 'N'<<8|'F', - 'N'<<8|'G', 'N'<<8|'I', 'N'<<8|'L', 'N'<<8|'O', 'N'<<8|'P', - 'N'<<8|'R', 'N'<<8|'U', 'N'<<8|'Z', 'O'<<8|'M', 'P'<<8|'A', - 'P'<<8|'E', 'P'<<8|'F', 'P'<<8|'G', 'P'<<8|'H', 'P'<<8|'K', - 'P'<<8|'L', 'P'<<8|'M', 'P'<<8|'N', 'P'<<8|'R', 'P'<<8|'S', - 'P'<<8|'T', 'P'<<8|'W', 'P'<<8|'Y', 'Q'<<8|'A', 'R'<<8|'E', - 'R'<<8|'O', 'R'<<8|'S', 'R'<<8|'U', 'R'<<8|'W', 'S'<<8|'A', - 'S'<<8|'B', 'S'<<8|'C', 'S'<<8|'D', 'S'<<8|'E', 'S'<<8|'G', - 'S'<<8|'H', 'S'<<8|'I', 'S'<<8|'J', 'S'<<8|'K', 'S'<<8|'L', - 'S'<<8|'M', 'S'<<8|'N', 'S'<<8|'O', 'S'<<8|'R', 'S'<<8|'T', - 'S'<<8|'V', 'S'<<8|'Y', 'S'<<8|'Z', 'T'<<8|'C', 'T'<<8|'D', - 'T'<<8|'F', 'T'<<8|'G', 'T'<<8|'H', 'T'<<8|'J', 'T'<<8|'K', - 'T'<<8|'L', 'T'<<8|'M', 'T'<<8|'N', 'T'<<8|'O', 'T'<<8|'R', - 'T'<<8|'T', 'T'<<8|'V', 'T'<<8|'W', 'T'<<8|'Z', 'U'<<8|'A', - 'U'<<8|'G', 'U'<<8|'M', 'U'<<8|'S', 'U'<<8|'Y', 'U'<<8|'Z', - 'V'<<8|'A', 'V'<<8|'C', 'V'<<8|'E', 'V'<<8|'G', 'V'<<8|'I', - 'V'<<8|'N', 'V'<<8|'U', 'W'<<8|'F', 'W'<<8|'S', 'Y'<<8|'E', - 'Y'<<8|'T', 'Z'<<8|'A', 'Z'<<8|'M', 'Z'<<8|'W', -1 }; + const int kCountryIds[] = {'A' << 8 | 'D', 'A' << 8 | 'E', 'A' << 8 | 'F', + 'A' << 8 | 'G', 'A' << 8 | 'I', 'A' << 8 | 'L', + 'A' << 8 | 'M', 'A' << 8 | 'N', 'A' << 8 | 'O', + 'A' << 8 | 'Q', 'A' << 8 | 'R', 'A' << 8 | 'S', + 'A' << 8 | 'T', 'A' << 8 | 'U', 'A' << 8 | 'W', + 'A' << 8 | 'X', 'A' << 8 | 'Z', 'B' << 8 | 'A', + 'B' << 8 | 'B', 'B' << 8 | 'D', 'B' << 8 | 'E', + 'B' << 8 | 'F', 'B' << 8 | 'G', 'B' << 8 | 'H', + 'B' << 8 | 'I', 'B' << 8 | 'J', 'B' << 8 | 'M', + 'B' << 8 | 'N', 'B' << 8 | 'O', 'B' << 8 | 'R', + 'B' << 8 | 'S', 'B' << 8 | 'T', 'B' << 8 | 'V', + 'B' << 8 | 'W', 'B' << 8 | 'Y', 'B' << 8 | 'Z', + 'C' << 8 | 'A', 'C' << 8 | 'C', 'C' << 8 | 'D', + 'C' << 8 | 'F', 'C' << 8 | 'G', 'C' << 8 | 'H', + 'C' << 8 | 'I', 'C' << 8 | 'K', 'C' << 8 | 'L', + 'C' << 8 | 'M', 'C' << 8 | 'N', 'C' << 8 | 'O', + 'C' << 8 | 'Q', 'C' << 8 | 'R', 'C' << 8 | 'U', + 'C' << 8 | 'V', 'C' << 8 | 'X', 'C' << 8 | 'Y', + 'C' << 8 | 'Z', 'D' << 8 | 'E', 'D' << 8 | 'J', + 'D' << 8 | 'K', 'D' << 8 | 'M', 'D' << 8 | 'O', + 'D' << 8 | 'Z', 'E' << 8 | 'C', 'E' << 8 | 'E', + 'E' << 8 | 'G', 'E' << 8 | 'R', 'E' << 8 | 'S', + 'E' << 8 | 'T', 'F' << 8 | 'I', 'F' << 8 | 'J', + 'F' << 8 | 'K', 'F' << 8 | 'M', 'F' << 8 | 'O', + 'F' << 8 | 'R', 'G' << 8 | 'A', 'G' << 8 | 'B', + 'G' << 8 | 'D', 'G' << 8 | 'E', 'G' << 8 | 'F', + 'G' << 8 | 'G', 'G' << 8 | 'H', 'G' << 8 | 'I', + 'G' << 8 | 'L', 'G' << 8 | 'M', 'G' << 8 | 'N', + 'G' << 8 | 'P', 'G' << 8 | 'Q', 'G' << 8 | 'R', + 'G' << 8 | 'S', 'G' << 8 | 'T', 'G' << 8 | 'U', + 'G' << 8 | 'W', 'G' << 8 | 'Y', 'H' << 8 | 'K', + 'H' << 8 | 'M', 'H' << 8 | 'N', 'H' << 8 | 'R', + 'H' << 8 | 'T', 'H' << 8 | 'U', 'I' << 8 | 'D', + 'I' << 8 | 'E', 'I' << 8 | 'L', 'I' << 8 | 'M', + 'I' << 8 | 'N', 'I' << 8 | 'O', 'I' << 8 | 'P', + 'I' << 8 | 'Q', 'I' << 8 | 'R', 'I' << 8 | 'S', + 'I' << 8 | 'T', 'J' << 8 | 'E', 'J' << 8 | 'M', + 'J' << 8 | 'O', 'J' << 8 | 'P', 'K' << 8 | 'E', + 'K' << 8 | 'G', 'K' << 8 | 'H', 'K' << 8 | 'I', + 'K' << 8 | 'M', 'K' << 8 | 'N', 'K' << 8 | 'P', + 'K' << 8 | 'R', 'K' << 8 | 'W', 'K' << 8 | 'Y', + 'K' << 8 | 'Z', 'L' << 8 | 'A', 'L' << 8 | 'B', + 'L' << 8 | 'C', 'L' << 8 | 'I', 'L' << 8 | 'K', + 'L' << 8 | 'R', 'L' << 8 | 'S', 'L' << 8 | 'T', + 'L' << 8 | 'U', 'L' << 8 | 'V', 'L' << 8 | 'Y', + 'M' << 8 | 'A', 'M' << 8 | 'C', 'M' << 8 | 'D', + 'M' << 8 | 'E', 'M' << 8 | 'G', 'M' << 8 | 'H', + 'M' << 8 | 'K', 'M' << 8 | 'L', 'M' << 8 | 'M', + 'M' << 8 | 'N', 'M' << 8 | 'O', 'M' << 8 | 'P', + 'M' << 8 | 'Q', 'M' << 8 | 'R', 'M' << 8 | 'S', + 'M' << 8 | 'T', 'M' << 8 | 'U', 'M' << 8 | 'V', + 'M' << 8 | 'W', 'M' << 8 | 'X', 'M' << 8 | 'Y', + 'M' << 8 | 'Z', 'N' << 8 | 'A', 'N' << 8 | 'C', + 'N' << 8 | 'E', 'N' << 8 | 'F', 'N' << 8 | 'G', + 'N' << 8 | 'I', 'N' << 8 | 'L', 'N' << 8 | 'O', + 'N' << 8 | 'P', 'N' << 8 | 'R', 'N' << 8 | 'U', + 'N' << 8 | 'Z', 'O' << 8 | 'M', 'P' << 8 | 'A', + 'P' << 8 | 'E', 'P' << 8 | 'F', 'P' << 8 | 'G', + 'P' << 8 | 'H', 'P' << 8 | 'K', 'P' << 8 | 'L', + 'P' << 8 | 'M', 'P' << 8 | 'N', 'P' << 8 | 'R', + 'P' << 8 | 'S', 'P' << 8 | 'T', 'P' << 8 | 'W', + 'P' << 8 | 'Y', 'Q' << 8 | 'A', 'R' << 8 | 'E', + 'R' << 8 | 'O', 'R' << 8 | 'S', 'R' << 8 | 'U', + 'R' << 8 | 'W', 'S' << 8 | 'A', 'S' << 8 | 'B', + 'S' << 8 | 'C', 'S' << 8 | 'D', 'S' << 8 | 'E', + 'S' << 8 | 'G', 'S' << 8 | 'H', 'S' << 8 | 'I', + 'S' << 8 | 'J', 'S' << 8 | 'K', 'S' << 8 | 'L', + 'S' << 8 | 'M', 'S' << 8 | 'N', 'S' << 8 | 'O', + 'S' << 8 | 'R', 'S' << 8 | 'T', 'S' << 8 | 'V', + 'S' << 8 | 'Y', 'S' << 8 | 'Z', 'T' << 8 | 'C', + 'T' << 8 | 'D', 'T' << 8 | 'F', 'T' << 8 | 'G', + 'T' << 8 | 'H', 'T' << 8 | 'J', 'T' << 8 | 'K', + 'T' << 8 | 'L', 'T' << 8 | 'M', 'T' << 8 | 'N', + 'T' << 8 | 'O', 'T' << 8 | 'R', 'T' << 8 | 'T', + 'T' << 8 | 'V', 'T' << 8 | 'W', 'T' << 8 | 'Z', + 'U' << 8 | 'A', 'U' << 8 | 'G', 'U' << 8 | 'M', + 'U' << 8 | 'S', 'U' << 8 | 'Y', 'U' << 8 | 'Z', + 'V' << 8 | 'A', 'V' << 8 | 'C', 'V' << 8 | 'E', + 'V' << 8 | 'G', 'V' << 8 | 'I', 'V' << 8 | 'N', + 'V' << 8 | 'U', 'W' << 8 | 'F', 'W' << 8 | 'S', + 'Y' << 8 | 'E', 'Y' << 8 | 'T', 'Z' << 8 | 'A', + 'Z' << 8 | 'M', 'Z' << 8 | 'W', -1}; for (size_t i = 0; i < std::size(kCountryIds); ++i) { prefs_.SetInteger(country_codes::kCountryIDAtInstall, kCountryIds[i]);
diff --git a/components/segmentation_platform/internal/execution/model_manager_impl.cc b/components/segmentation_platform/internal/execution/model_manager_impl.cc index 01f7d200..e144eef8 100644 --- a/components/segmentation_platform/internal/execution/model_manager_impl.cc +++ b/components/segmentation_platform/internal/execution/model_manager_impl.cc
@@ -98,8 +98,7 @@ int64_t model_version) { TRACE_EVENT("segmentation_platform", "ModelManagerImpl::OnSegmentationModelUpdated"); - // TODO(ritikagup@) : Add a variant for default model separately. - stats::RecordModelDeliveryReceived(segment_id); + stats::RecordModelDeliveryReceived(segment_id, model_source); if (segment_id == proto::SegmentId::OPTIMIZATION_TARGET_UNKNOWN) { return; } @@ -109,9 +108,8 @@ metadata_utils::SetFeatureNameHashesFromName(&metadata); auto validation = metadata_utils::ValidateMetadataAndFeatures(metadata); - // TODO(ritikagup@) : Add a variant for default model separately. stats::RecordModelDeliveryMetadataValidation( - segment_id, /* processed = */ false, validation); + segment_id, model_source, /* processed = */ false, validation); if (validation != metadata_utils::ValidationResult::kValidationSuccess) { return; } @@ -153,9 +151,8 @@ // If does not match, we should just overwrite the old entry with one // that has a matching segment ID, otherwise we will keep ignoring it // forever and never be able to clean it up. - // TODO(ritikagup@) : Add a variant for default model separately. stats::RecordModelDeliverySegmentIdMatches( - new_segment_info.segment_id(), + new_segment_info.segment_id(), model_source, new_segment_info.segment_id() == old_segment_info->segment_id()); if (old_segment_info->has_model_version()) { @@ -184,16 +181,15 @@ // unless the metadata is valid. auto validation = metadata_utils::ValidateSegmentInfoMetadataAndFeatures(new_segment_info); - // TODO(ritikagup@) : Add a variant for default model separately. stats::RecordModelDeliveryMetadataValidation( - segment_id, /* processed = */ true, validation); + segment_id, model_source, /* processed = */ true, validation); if (validation != metadata_utils::ValidationResult::kValidationSuccess) { return; } - // TODO(ritikagup@) : Add a variant for default model separately. stats::RecordModelDeliveryMetadataFeatureCount( - segment_id, new_segment_info.model_metadata().features_size()); + segment_id, model_source, + new_segment_info.model_metadata().features_size()); // Now that we've merged the old and the new SegmentInfo, we want to store // the new version in the database. segment_database_->UpdateSegment( @@ -208,8 +204,8 @@ TRACE_EVENT("segmentation_platform", "ModelManagerImpl::OnUpdatedSegmentInfoStored"); - // TODO(ritikagup@) : Add a variant for default model separately. - stats::RecordModelDeliverySaveResult(segment_info.segment_id(), success); + stats::RecordModelDeliverySaveResult(segment_info.segment_id(), + segment_info.model_source(), success); if (!success) { return; }
diff --git a/components/segmentation_platform/internal/selection/request_handler_unittest.cc b/components/segmentation_platform/internal/selection/request_handler_unittest.cc index 2b07a3f..9bd797eb 100644 --- a/components/segmentation_platform/internal/selection/request_handler_unittest.cc +++ b/components/segmentation_platform/internal/selection/request_handler_unittest.cc
@@ -68,6 +68,19 @@ return result; } +proto::PredictionResult CreatePredictionResultWithGenericPredictor() { + proto::SegmentationModelMetadata model_metadata; + MetadataWriter writer(&model_metadata); + writer.AddOutputConfigForGenericPredictor({"output1", "output2"}); + + proto::PredictionResult prediction_result; + prediction_result.add_result(0.8f); + prediction_result.add_result(0.2f); + prediction_result.mutable_output_config()->Swap( + model_metadata.mutable_output_config()); + return prediction_result; +} + class RequestHandlerTest : public testing::Test { public: RequestHandlerTest() = default; @@ -135,5 +148,35 @@ loop.Run(); } +TEST_F(RequestHandlerTest, GetGenericPredictionResult) { + PredictionOptions options; + options.on_demand_execution = true; + + EXPECT_CALL(*training_data_collector_, + OnDecisionTime(kSegmentId, _, + proto::TrainingOutputs::TriggerConfig::ONDEMAND)) + .WillOnce(Return(TrainingRequestId::FromUnsafeValue(15))); + EXPECT_CALL(*result_provider_, GetSegmentResult(_)) + .WillOnce(Invoke( + [](std::unique_ptr<SegmentResultProvider::GetResultOptions> options) { + EXPECT_TRUE(options->ignore_db_scores); + EXPECT_EQ(options->segment_id, kSegmentId); + auto result = + std::make_unique<SegmentResultProvider::SegmentResult>( + SegmentResultProvider::ResultState::kTfliteModelScoreUsed, + CreatePredictionResultWithGenericPredictor(), + /*rank=*/2); + std::move(options->callback).Run(std::move(result)); + })); + + base::RunLoop loop; + RawResult result(PredictionStatus::kFailed); + request_handler_->GetPredictionResult( + options, scoped_refptr<InputContext>(), + base::BindOnce(&RequestHandlerTest::OnGetPredictionResult, + base::Unretained(this), loop.QuitClosure())); + loop.Run(); +} + } // namespace } // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/stats.cc b/components/segmentation_platform/internal/stats.cc index 307e9d73..1820e25e5 100644 --- a/components/segmentation_platform/internal/stats.cc +++ b/components/segmentation_platform/internal/stats.cc
@@ -12,6 +12,7 @@ #include "components/segmentation_platform/internal/post_processor/post_processor.h" #include "components/segmentation_platform/public/constants.h" #include "components/segmentation_platform/public/proto/model_metadata.pb.h" +#include "components/segmentation_platform/public/proto/output_config.pb.h" #include "components/segmentation_platform/public/proto/segmentation_platform.pb.h" #include "components/segmentation_platform/public/proto/types.pb.h" @@ -194,6 +195,12 @@ return static_cast<float>(zero_values) / static_cast<float>(tensor.size()); } +// For server models to keep the same name as before, empty string is returned. +std::string GetModelSourceAsString(proto::ModelSource model_source) { + // Should map to ModelSource variant string in + // //tools/metrics/histograms/metadata/segmentation_platform/histograms.xml. + return (model_source == proto::DEFAULT_MODEL_SOURCE ? "Default" : ""); +} } // namespace void RecordModelUpdateTimeDifference(SegmentId segment_id, @@ -251,6 +258,10 @@ void RecordClassificationResultComputed( const Config& config, const proto::PredictionResult& new_result) { + if (new_result.output_config().predictor().PredictorType_case() == + proto::Predictor::kGenericPredictor) { + return; + } PostProcessor post_processor; int new_result_top_label = post_processor.GetIndexOfTopLabel(new_result); std::string computed_hist = @@ -323,42 +334,55 @@ } void RecordModelDeliveryMetadataFeatureCount(SegmentId segment_id, + ModelSource model_source, size_t count) { - base::UmaHistogramCounts1000( - "SegmentationPlatform.ModelDelivery.Metadata.FeatureCount." + - SegmentIdToHistogramVariant(segment_id), - count); + base::UmaHistogramCounts1000("SegmentationPlatform." + + GetModelSourceAsString(model_source) + + "ModelDelivery.Metadata.FeatureCount." + + SegmentIdToHistogramVariant(segment_id), + count); } void RecordModelDeliveryMetadataValidation( SegmentId segment_id, + proto::ModelSource model_source, bool processed, metadata_utils::ValidationResult validation_result) { // Should map to ValidationPhase variant string in // //tools/metrics/histograms/metadata/segmentation_platform/histograms.xml. std::string validation_phase = processed ? "Processed" : "Incoming"; base::UmaHistogramEnumeration( - "SegmentationPlatform.ModelDelivery.Metadata.Validation." + - validation_phase + "." + SegmentIdToHistogramVariant(segment_id), + "SegmentationPlatform." + GetModelSourceAsString(model_source) + + "ModelDelivery.Metadata.Validation." + validation_phase + "." + + SegmentIdToHistogramVariant(segment_id), validation_result); } -void RecordModelDeliveryReceived(SegmentId segment_id) { - base::UmaHistogramSparse("SegmentationPlatform.ModelDelivery.Received", +void RecordModelDeliveryReceived(SegmentId segment_id, + proto::ModelSource model_source) { + base::UmaHistogramSparse("SegmentationPlatform." + + GetModelSourceAsString(model_source) + + "ModelDelivery.Received", segment_id); } -void RecordModelDeliverySaveResult(SegmentId segment_id, bool success) { - base::UmaHistogramBoolean("SegmentationPlatform.ModelDelivery.SaveResult." + - SegmentIdToHistogramVariant(segment_id), - success); +void RecordModelDeliverySaveResult(SegmentId segment_id, + proto::ModelSource model_source, + bool success) { + base::UmaHistogramBoolean( + "SegmentationPlatform." + GetModelSourceAsString(model_source) + + "ModelDelivery.SaveResult." + SegmentIdToHistogramVariant(segment_id), + success); } -void RecordModelDeliverySegmentIdMatches(SegmentId segment_id, bool matches) { - base::UmaHistogramBoolean( - "SegmentationPlatform.ModelDelivery.SegmentIdMatches." + - SegmentIdToHistogramVariant(segment_id), - matches); +void RecordModelDeliverySegmentIdMatches(SegmentId segment_id, + proto::ModelSource model_source, + bool matches) { + base::UmaHistogramBoolean("SegmentationPlatform." + + GetModelSourceAsString(model_source) + + "ModelDelivery.SegmentIdMatches." + + SegmentIdToHistogramVariant(segment_id), + matches); } void RecordModelExecutionDurationFeatureProcessing(SegmentId segment_id,
diff --git a/components/segmentation_platform/internal/stats.h b/components/segmentation_platform/internal/stats.h index b8be4e3..53ed58e1 100644 --- a/components/segmentation_platform/internal/stats.h +++ b/components/segmentation_platform/internal/stats.h
@@ -86,27 +86,35 @@ void RecordMaintenanceSignalIdentifierCount(size_t count); // Model Delivery metrics. -// Records whether any incoming ML model had metadata attached that we were able -// to parse. +// Records whether any incoming ML had metadata attached that +// we were able to parse. void RecordModelDeliveryHasMetadata(SegmentId segment_id, bool has_metadata); -// Records the number of tensor features an updated ML model has. +// Records the number of tensor features an updated server or embedded model +// has. void RecordModelDeliveryMetadataFeatureCount(SegmentId segment_id, + proto::ModelSource model_source, size_t count); -// Records the result of validating the metadata of an incoming ML model. -// Recorded before and after it has been merged with the already stored -// metadata. +// Records the result of validating the metadata of an incoming server or +// embedded model. Recorded before and after it has been merged with the already +// stored metadata. void RecordModelDeliveryMetadataValidation( SegmentId segment_id, + proto::ModelSource model_source, bool processed, metadata_utils::ValidationResult validation_result); -// Record what type of model metadata we received. -void RecordModelDeliveryReceived(SegmentId segment_id); -// Records the result of attempting to save an updated version of the model -// metadata. -void RecordModelDeliverySaveResult(SegmentId segment_id, bool success); +// Record what type of server or embedded model metadata we received . +void RecordModelDeliveryReceived(SegmentId segment_id, + proto::ModelSource model_source); +// Records the result of attempting to save an updated version of the server or +// embedded model metadata. +void RecordModelDeliverySaveResult(SegmentId segment_id, + proto::ModelSource model_source, + bool success); // Records whether the currently stored segment_id matches the incoming -// segment_id, as these are expected to match. -void RecordModelDeliverySegmentIdMatches(SegmentId segment_id, bool matches); +// segment_id for a particular model_source, as these are expected to match. +void RecordModelDeliverySegmentIdMatches(SegmentId segment_id, + proto::ModelSource model_source, + bool matches); // Model Execution metrics. // Records the duration of processing a single ML feature. This only takes into
diff --git a/components/segmentation_platform/public/segmentation_platform_service.cc b/components/segmentation_platform/public/segmentation_platform_service.cc index 25edfbc7..ed87149 100644 --- a/components/segmentation_platform/public/segmentation_platform_service.cc +++ b/components/segmentation_platform/public/segmentation_platform_service.cc
@@ -9,6 +9,8 @@ TrainingLabels::TrainingLabels() = default; TrainingLabels::~TrainingLabels() = default; +TrainingLabels::TrainingLabels(const TrainingLabels& other) = default; + ServiceProxy* SegmentationPlatformService::GetServiceProxy() { return nullptr; }
diff --git a/components/segmentation_platform/public/segmentation_platform_service.h b/components/segmentation_platform/public/segmentation_platform_service.h index 5d1549c..e28230a 100644 --- a/components/segmentation_platform/public/segmentation_platform_service.h +++ b/components/segmentation_platform/public/segmentation_platform_service.h
@@ -44,6 +44,8 @@ // Name and sample of the output metric to be collected as training data. absl::optional<std::pair<std::string, base::HistogramBase::Sample>> output_metric; + + TrainingLabels(const TrainingLabels& other); }; // The core class of segmentation platform that integrates all the required
diff --git a/components/send_tab_to_self/entry_point_display_reason.cc b/components/send_tab_to_self/entry_point_display_reason.cc index 7076c8c0..3e3d2aff 100644 --- a/components/send_tab_to_self/entry_point_display_reason.cc +++ b/components/send_tab_to_self/entry_point_display_reason.cc
@@ -24,7 +24,7 @@ #if BUILDFLAG(IS_CHROMEOS_LACROS) return false; #else - return pref_service->GetBoolean(prefs::kSigninAllowed) && sync_service && + return pref_service->GetBoolean(prefs::kSigninAllowed) && sync_service->GetAccountInfo().IsEmpty() && !sync_service->HasDisableReason( syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY) && @@ -34,16 +34,19 @@ } // namespace +namespace internal { + absl::optional<EntryPointDisplayReason> GetEntryPointDisplayReason( const GURL& url_to_share, syncer::SyncService* sync_service, - SendTabToSelfSyncService* send_tab_to_self_sync_service, + SendTabToSelfModel* send_tab_to_self_model, PrefService* pref_service) { - if (!url_to_share.SchemeIsHTTPOrHTTPS()) + if (!url_to_share.SchemeIsHTTPOrHTTPS()) { return absl::nullopt; + } - if (!send_tab_to_self_sync_service || !sync_service) { - // Can happen in incognito, guest profile, or tests. + if (!send_tab_to_self_model || !sync_service) { + // Send-tab-to-self can't work properly, don't show the entry point. return absl::nullopt; } @@ -52,9 +55,7 @@ return EntryPointDisplayReason::kOfferSignIn; } - SendTabToSelfModel* model = - send_tab_to_self_sync_service->GetSendTabToSelfModel(); - if (!model->IsReady()) { + if (!send_tab_to_self_model->IsReady()) { syncer::SyncUserSettings* settings = sync_service->GetUserSettings(); if (sync_service->IsEngineInitialized() && (settings->IsPassphraseRequiredForPreferredDataTypes() || @@ -67,7 +68,7 @@ return absl::nullopt; } - if (!model->HasValidTargetDevice()) { + if (!send_tab_to_self_model->HasValidTargetDevice()) { return base::FeatureList::IsEnabled(kSendTabToSelfSigninPromo) ? absl::make_optional( EntryPointDisplayReason::kInformNoTargetDevice) @@ -77,4 +78,6 @@ return EntryPointDisplayReason::kOfferFeature; } +} // namespace internal + } // namespace send_tab_to_self
diff --git a/components/send_tab_to_self/entry_point_display_reason.h b/components/send_tab_to_self/entry_point_display_reason.h index bc09c54..34b7912 100644 --- a/components/send_tab_to_self/entry_point_display_reason.h +++ b/components/send_tab_to_self/entry_point_display_reason.h
@@ -16,7 +16,7 @@ namespace send_tab_to_self { -class SendTabToSelfSyncService; +class SendTabToSelfModel; // A Java counterpart will be generated for this enum. // GENERATED_JAVA_ENUM_PACKAGE: ( @@ -35,13 +35,20 @@ kInformNoTargetDevice, }; -// |sync_service| and |send_tab_to_self_sync_service| can be null. +namespace internal { + +// Use SendTabToSelfService::GetEntryPointDisplayReason() instead. +// `sync_service` and `send_tab_to_self_sync_model` can be null and that's +// handled as if send-tab-to-self or the sync backbone aren't working, so the +// entry point should be hidden (returns nullopt). absl::optional<EntryPointDisplayReason> GetEntryPointDisplayReason( const GURL& url_to_share, syncer::SyncService* sync_service, - SendTabToSelfSyncService* send_tab_to_self_sync_service, + SendTabToSelfModel* send_tab_to_self_model, PrefService* pref_service); +} // namespace internal + } // namespace send_tab_to_self #endif // COMPONENTS_SEND_TAB_TO_SELF_ENTRY_POINT_DISPLAY_REASON_H_
diff --git a/components/send_tab_to_self/entry_point_display_reason_unittest.cc b/components/send_tab_to_self/entry_point_display_reason_unittest.cc index e64826d..fa1ba82 100644 --- a/components/send_tab_to_self/entry_point_display_reason_unittest.cc +++ b/components/send_tab_to_self/entry_point_display_reason_unittest.cc
@@ -24,6 +24,8 @@ namespace { +using internal::GetEntryPointDisplayReason; + const char kHttpsUrl[] = "https://www.foo.com"; const char kHttpUrl[] = "http://www.foo.com"; @@ -48,17 +50,6 @@ bool has_valid_target_device_ = false; }; -class FakeSendTabToSelfSyncService : public SendTabToSelfSyncService { - public: - FakeSendTabToSelfSyncService() = default; - ~FakeSendTabToSelfSyncService() override = default; - - FakeSendTabToSelfModel* GetSendTabToSelfModel() override { return &model_; } - - private: - FakeSendTabToSelfModel model_; -}; - class EntryPointDisplayReasonTest : public ::testing::Test { public: EntryPointDisplayReasonTest() { @@ -66,8 +57,8 @@ } syncer::TestSyncService* sync_service() { return &sync_service_; } - FakeSendTabToSelfSyncService* send_tab_to_self_sync_service() { - return &send_tab_to_self_sync_service_; + FakeSendTabToSelfModel* send_tab_to_self_model() { + return &send_tab_to_self_model_; } TestingPrefServiceSimple* pref_service() { return &pref_service_; } @@ -81,7 +72,7 @@ private: syncer::TestSyncService sync_service_; - FakeSendTabToSelfSyncService send_tab_to_self_sync_service_; + FakeSendTabToSelfModel send_tab_to_self_model_; TestingPrefServiceSimple pref_service_; }; @@ -91,7 +82,7 @@ feature_list.InitAndDisableFeature(kSendTabToSelfSigninPromo); EXPECT_FALSE(GetEntryPointDisplayReason(GURL(kHttpsUrl), sync_service(), - send_tab_to_self_sync_service(), + send_tab_to_self_model(), pref_service())); } @@ -102,10 +93,10 @@ base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeature(kSendTabToSelfSigninPromo); - EXPECT_EQ(EntryPointDisplayReason::kOfferSignIn, - GetEntryPointDisplayReason(GURL(kHttpsUrl), sync_service(), - send_tab_to_self_sync_service(), - pref_service())); + EXPECT_EQ( + EntryPointDisplayReason::kOfferSignIn, + GetEntryPointDisplayReason(GURL(kHttpsUrl), sync_service(), + send_tab_to_self_model(), pref_service())); } #endif // !BUILDFLAG(IS_CHROMEOS_LACROS) @@ -118,19 +109,17 @@ syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY}); EXPECT_FALSE(GetEntryPointDisplayReason(GURL(kHttpsUrl), sync_service(), - send_tab_to_self_sync_service(), + send_tab_to_self_model(), pref_service())); } TEST_F(EntryPointDisplayReasonTest, ShouldHideEntryPointIfModelNotReady) { SignIn(); - send_tab_to_self_sync_service()->GetSendTabToSelfModel()->SetIsReady(false); - send_tab_to_self_sync_service() - ->GetSendTabToSelfModel() - ->SetHasValidTargetDevice(false); + send_tab_to_self_model()->SetIsReady(false); + send_tab_to_self_model()->SetHasValidTargetDevice(false); EXPECT_FALSE(GetEntryPointDisplayReason(GURL(kHttpsUrl), sync_service(), - send_tab_to_self_sync_service(), + send_tab_to_self_model(), pref_service())); } @@ -140,13 +129,11 @@ feature_list.InitAndDisableFeature(kSendTabToSelfSigninPromo); SignIn(); - send_tab_to_self_sync_service()->GetSendTabToSelfModel()->SetIsReady(true); - send_tab_to_self_sync_service() - ->GetSendTabToSelfModel() - ->SetHasValidTargetDevice(false); + send_tab_to_self_model()->SetIsReady(true); + send_tab_to_self_model()->SetHasValidTargetDevice(false); EXPECT_FALSE(GetEntryPointDisplayReason(GURL(kHttpsUrl), sync_service(), - send_tab_to_self_sync_service(), + send_tab_to_self_model(), pref_service())); } @@ -156,57 +143,45 @@ feature_list.InitAndEnableFeature(kSendTabToSelfSigninPromo); SignIn(); - send_tab_to_self_sync_service()->GetSendTabToSelfModel()->SetIsReady(true); - send_tab_to_self_sync_service() - ->GetSendTabToSelfModel() - ->SetHasValidTargetDevice(false); + send_tab_to_self_model()->SetIsReady(true); + send_tab_to_self_model()->SetHasValidTargetDevice(false); - EXPECT_EQ(EntryPointDisplayReason::kInformNoTargetDevice, - GetEntryPointDisplayReason(GURL(kHttpsUrl), sync_service(), - send_tab_to_self_sync_service(), - pref_service())); + EXPECT_EQ( + EntryPointDisplayReason::kInformNoTargetDevice, + GetEntryPointDisplayReason(GURL(kHttpsUrl), sync_service(), + send_tab_to_self_model(), pref_service())); } TEST_F(EntryPointDisplayReasonTest, ShouldOnlyOfferFeatureIfHttpOrHttps) { SignIn(); - send_tab_to_self_sync_service()->GetSendTabToSelfModel()->SetIsReady(true); - send_tab_to_self_sync_service() - ->GetSendTabToSelfModel() - ->SetHasValidTargetDevice(true); + send_tab_to_self_model()->SetIsReady(true); + send_tab_to_self_model()->SetHasValidTargetDevice(true); - EXPECT_EQ(EntryPointDisplayReason::kOfferFeature, - GetEntryPointDisplayReason(GURL(kHttpsUrl), sync_service(), - send_tab_to_self_sync_service(), - pref_service())); + EXPECT_EQ( + EntryPointDisplayReason::kOfferFeature, + GetEntryPointDisplayReason(GURL(kHttpsUrl), sync_service(), + send_tab_to_self_model(), pref_service())); - EXPECT_EQ(EntryPointDisplayReason::kOfferFeature, - GetEntryPointDisplayReason(GURL(kHttpUrl), sync_service(), - send_tab_to_self_sync_service(), - pref_service())); + EXPECT_EQ( + EntryPointDisplayReason::kOfferFeature, + GetEntryPointDisplayReason(GURL(kHttpUrl), sync_service(), + send_tab_to_self_model(), pref_service())); EXPECT_FALSE(GetEntryPointDisplayReason(GURL("192.168.0.0"), sync_service(), - send_tab_to_self_sync_service(), + send_tab_to_self_model(), pref_service())); - EXPECT_FALSE(GetEntryPointDisplayReason( - GURL("chrome-untrusted://url"), sync_service(), - send_tab_to_self_sync_service(), pref_service())); + EXPECT_FALSE( + GetEntryPointDisplayReason(GURL("chrome-untrusted://url"), sync_service(), + send_tab_to_self_model(), pref_service())); - EXPECT_FALSE(GetEntryPointDisplayReason( - GURL("chrome://flags"), sync_service(), send_tab_to_self_sync_service(), - pref_service())); + EXPECT_FALSE( + GetEntryPointDisplayReason(GURL("chrome://flags"), sync_service(), + send_tab_to_self_model(), pref_service())); - EXPECT_FALSE(GetEntryPointDisplayReason( - GURL("tel:07399999999"), sync_service(), send_tab_to_self_sync_service(), - pref_service())); -} - -TEST_F(EntryPointDisplayReasonTest, ShouldHideEntryPointInIncognitoMode) { - // Note: if changing this, audit profile-finding logic in the feature. - // For example, NotificationManager.java in the Android code assumes - // incognito is not supported. - EXPECT_FALSE(GetEntryPointDisplayReason(GURL(kHttpsUrl), nullptr, nullptr, - pref_service())); + EXPECT_FALSE( + GetEntryPointDisplayReason(GURL("tel:07399999999"), sync_service(), + send_tab_to_self_model(), pref_service())); } } // namespace
diff --git a/components/send_tab_to_self/send_tab_to_self_sync_service.cc b/components/send_tab_to_self/send_tab_to_self_sync_service.cc index e35c507..a80c781 100644 --- a/components/send_tab_to_self/send_tab_to_self_sync_service.cc +++ b/components/send_tab_to_self/send_tab_to_self_sync_service.cc
@@ -19,12 +19,13 @@ namespace send_tab_to_self { -SendTabToSelfSyncService::SendTabToSelfSyncService() = default; +SendTabToSelfSyncService::SendTabToSelfSyncService() : pref_service_(nullptr) {} SendTabToSelfSyncService::SendTabToSelfSyncService( version_info::Channel channel, syncer::OnceModelTypeStoreFactory create_store_callback, history::HistoryService* history_service, + PrefService* pref_service, syncer::DeviceInfoTracker* device_info_tracker) : bridge_(std::make_unique<send_tab_to_self::SendTabToSelfBridge>( std::make_unique<syncer::ClientTagBasedModelTypeProcessor>( @@ -33,10 +34,33 @@ base::DefaultClock::GetInstance(), std::move(create_store_callback), history_service, - device_info_tracker)) {} + device_info_tracker)), + pref_service_(pref_service) {} SendTabToSelfSyncService::~SendTabToSelfSyncService() = default; +void SendTabToSelfSyncService::OnSyncServiceInitialized( + syncer::SyncService* sync_service) { + sync_service_ = sync_service; + sync_service_->AddObserver(this); +} + +absl::optional<EntryPointDisplayReason> +SendTabToSelfSyncService::GetEntryPointDisplayReason(const GURL& url_to_share) { + // `sync_service_` can be null in any of these cases. In all of them the + // handling is correct because sync is not available (Yet? Anymore?). + // a) OnSyncServiceInitialized() didn't get called yet. + // b) OnSyncShutdown() already got called. + // c) This is a test that didn't fake the SyncService. + // d) Sync got disabled by command-line flag. + // `bridge_` might be null for fake subclasses that invoked the default + // constructor. + // TODO(crbug.com/1473353): Split interface out of this class and CHECK for + // SendTabToSelfModel in the method below. + return internal::GetEntryPointDisplayReason(url_to_share, sync_service_, + bridge_.get(), pref_service_); +} + SendTabToSelfModel* SendTabToSelfSyncService::GetSendTabToSelfModel() { return bridge_.get(); } @@ -46,4 +70,9 @@ return bridge_->change_processor()->GetControllerDelegate(); } +void SendTabToSelfSyncService::OnSyncShutdown(syncer::SyncService*) { + sync_service_->RemoveObserver(this); + sync_service_ = nullptr; +} + } // namespace send_tab_to_self
diff --git a/components/send_tab_to_self/send_tab_to_self_sync_service.h b/components/send_tab_to_self/send_tab_to_self_sync_service.h index 52c90b6..6cdf4965e 100644 --- a/components/send_tab_to_self/send_tab_to_self_sync_service.h +++ b/components/send_tab_to_self/send_tab_to_self_sync_service.h
@@ -7,10 +7,17 @@ #include <memory> +#include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "components/keyed_service/core/keyed_service.h" +#include "components/send_tab_to_self/entry_point_display_reason.h" #include "components/sync/model/model_type_store_service.h" +#include "components/sync/service/sync_service_observer.h" #include "components/version_info/channel.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +class GURL; +class PrefService; namespace history { class HistoryService; @@ -19,6 +26,7 @@ namespace syncer { class DeviceInfoTracker; class ModelTypeControllerDelegate; +class SyncService; } // namespace syncer namespace send_tab_to_self { @@ -26,12 +34,14 @@ class SendTabToSelfModel; // KeyedService responsible for send tab to self sync. -class SendTabToSelfSyncService : public KeyedService { +class SendTabToSelfSyncService : public KeyedService, + public syncer::SyncServiceObserver { public: SendTabToSelfSyncService( version_info::Channel channel, syncer::OnceModelTypeStoreFactory create_store_callback, history::HistoryService* history_service, + PrefService* pref_service, syncer::DeviceInfoTracker* device_info_tracker); SendTabToSelfSyncService(const SendTabToSelfSyncService&) = delete; @@ -39,6 +49,13 @@ ~SendTabToSelfSyncService() override; + // Hooks the cyclic dependency. + void OnSyncServiceInitialized(syncer::SyncService* sync_service); + + // See EntryPointDisplayReason definition. Virtual for testing. + virtual absl::optional<EntryPointDisplayReason> GetEntryPointDisplayReason( + const GURL& url_to_share); + // Never returns null. virtual SendTabToSelfModel* GetSendTabToSelfModel(); @@ -50,7 +67,15 @@ SendTabToSelfSyncService(); private: + // SyncServiceObserver implementation. + void OnSyncShutdown(syncer::SyncService* sync_service) override; + std::unique_ptr<SendTabToSelfBridge> const bridge_; + raw_ptr<PrefService> const pref_service_; + + // Cyclic dependency, initialized in OnSyncServiceInitialized(), reset in + // OnSyncShutdown(). + raw_ptr<syncer::SyncService> sync_service_ = nullptr; }; } // namespace send_tab_to_self
diff --git a/components/user_education/common/BUILD.gn b/components/user_education/common/BUILD.gn index 33c8e18e..e1b96b8 100644 --- a/components/user_education/common/BUILD.gn +++ b/components/user_education/common/BUILD.gn
@@ -37,6 +37,8 @@ "tutorial_service.h", "user_education_class_properties.cc", "user_education_class_properties.h", + "user_education_features.cc", + "user_education_features.h", ] deps = [
diff --git a/components/user_education/common/user_education_features.cc b/components/user_education/common/user_education_features.cc new file mode 100644 index 0000000..0309214 --- /dev/null +++ b/components/user_education/common/user_education_features.cc
@@ -0,0 +1,15 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/user_education/common/user_education_features.h" + +#include "base/feature_list.h" + +namespace user_education::features { + +BASE_FEATURE(kUserEducationExperienceVersion2, + "UserEducationExperienceVersion2", + base::FEATURE_DISABLED_BY_DEFAULT); + +} // namespace user_education::features
diff --git a/components/user_education/common/user_education_features.h b/components/user_education/common/user_education_features.h new file mode 100644 index 0000000..f7488363 --- /dev/null +++ b/components/user_education/common/user_education_features.h
@@ -0,0 +1,16 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_USER_EDUCATION_COMMON_USER_EDUCATION_FEATURES_H_ +#define COMPONENTS_USER_EDUCATION_COMMON_USER_EDUCATION_FEATURES_H_ + +#include "base/feature_list.h" + +namespace user_education::features { + +BASE_DECLARE_FEATURE(kUserEducationExperienceVersion2); + +} // namespace user_education::features + +#endif // COMPONENTS_USER_EDUCATION_COMMON_USER_EDUCATION_FEATURES_H_
diff --git a/components/variations/cros/README.md b/components/variations/cros/README.md deleted file mode 100644 index 2cfc020..0000000 --- a/components/variations/cros/README.md +++ /dev/null
@@ -1,16 +0,0 @@ -# `evaluate_seed` - -The executable `evaluate_seed` is a minimal program (budget: 1MiB of disk space -for the executable itself) used early in ChromeOS boot to determine which group -each early-boot experiment should be in, as well as any parameters for the -experiment. It lives here so that it is trivial to keep the code in sync between -ChromeOS's platform layer and chrome. - -It will be executed primarily by `featured`, which lives in -`//platform2/featured/`. - -`featured` will set command-line parameters as appropriate (e.g. to determine -enterprise enrollment state and whether to use a safe seed), and will feed a -safe seed (if necessary) via stdin. - -`evaluate_seed` will write a serialized version of the computed state to stdout.
diff --git a/components/variations/cros/BUILD.gn b/components/variations/cros_evaluate_seed/BUILD.gn similarity index 100% rename from components/variations/cros/BUILD.gn rename to components/variations/cros_evaluate_seed/BUILD.gn
diff --git a/components/variations/cros/DEPS b/components/variations/cros_evaluate_seed/DEPS similarity index 100% rename from components/variations/cros/DEPS rename to components/variations/cros_evaluate_seed/DEPS
diff --git a/components/variations/cros/OWNERS b/components/variations/cros_evaluate_seed/OWNERS similarity index 100% rename from components/variations/cros/OWNERS rename to components/variations/cros_evaluate_seed/OWNERS
diff --git a/components/variations/cros_evaluate_seed/README.md b/components/variations/cros_evaluate_seed/README.md new file mode 100644 index 0000000..e03ef734 --- /dev/null +++ b/components/variations/cros_evaluate_seed/README.md
@@ -0,0 +1,27 @@ +# `cros_evaluate_seed` + +This directory is for code to evaluate field trial state early in boot on +ChromeOS. All code here will be used in a single program, `evaluate_seed`. It is +not for general use in ash (or lacros). + +## `evaluate_seed` + +The executable `evaluate_seed` is a minimal program (budget: 2MiB of disk space +for the executable itself) used early in ChromeOS boot to determine which group +each early-boot experiment should be in, as well as any parameters for the +experiment. It lives here so that it is trivial to keep the code in sync between +ChromeOS's platform layer and chrome. + +It will be built alongside ash, and use the same seed it uses in `Local State` +in normal operation. + +It will be executed primarily by `featured`, which lives in +`//platform2/featured/`. + +In safe seed cases, the **platform** code (featured) will determine whether to +use the safe (or null) seed, and pass that information along with the value of +the safe seed to use along to this code. + +`evaluate_seed` will write a serialized version of the computed state to stdout, +along with a representation of the seed used for computation (for purposes of +determining whether the seed can be marked as "safe").
diff --git a/components/variations/cros/cros_safe_seed_manager.cc b/components/variations/cros_evaluate_seed/cros_safe_seed_manager.cc similarity index 68% rename from components/variations/cros/cros_safe_seed_manager.cc rename to components/variations/cros_evaluate_seed/cros_safe_seed_manager.cc index d493c341..e7677641f 100644 --- a/components/variations/cros/cros_safe_seed_manager.cc +++ b/components/variations/cros_evaluate_seed/cros_safe_seed_manager.cc
@@ -2,12 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/variations/cros/cros_safe_seed_manager.h" +#include "components/variations/cros_evaluate_seed/cros_safe_seed_manager.h" #include "components/variations/service/safe_seed_manager_interface.h" -namespace variations { -namespace cros_early_boot { +namespace variations::cros_early_boot::evaluate_seed { CrOSSafeSeedManager::CrOSSafeSeedManager(SeedType seed) : seed_(seed) {} @@ -17,5 +16,4 @@ return seed_; } -} // namespace cros_early_boot -} // namespace variations +} // namespace variations::cros_early_boot::evaluate_seed
diff --git a/components/variations/cros/cros_safe_seed_manager.h b/components/variations/cros_evaluate_seed/cros_safe_seed_manager.h similarity index 82% rename from components/variations/cros/cros_safe_seed_manager.h rename to components/variations/cros_evaluate_seed/cros_safe_seed_manager.h index 0851a8a..e5684a7 100644 --- a/components/variations/cros/cros_safe_seed_manager.h +++ b/components/variations/cros_evaluate_seed/cros_safe_seed_manager.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef COMPONENTS_VARIATIONS_CROS_CROS_SAFE_SEED_MANAGER_H_ -#define COMPONENTS_VARIATIONS_CROS_CROS_SAFE_SEED_MANAGER_H_ +#ifndef COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_CROS_SAFE_SEED_MANAGER_H_ +#define COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_CROS_SAFE_SEED_MANAGER_H_ #include <memory> #include <string> @@ -13,8 +13,7 @@ class PrefRegistrySimple; -namespace variations { -namespace cros_early_boot { +namespace variations::cros_early_boot::evaluate_seed { class CrOSSafeSeedManager : public SafeSeedManagerInterface { public: @@ -52,7 +51,6 @@ SeedType seed_; }; -} // namespace cros_early_boot -} // namespace variations +} // namespace variations::cros_early_boot::evaluate_seed -#endif // COMPONENTS_VARIATIONS_CROS_CROS_SAFE_SEED_MANAGER_H_ +#endif // COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_CROS_SAFE_SEED_MANAGER_H_
diff --git a/components/variations/cros/cros_safe_seed_manager_unittest.cc b/components/variations/cros_evaluate_seed/cros_safe_seed_manager_unittest.cc similarity index 77% rename from components/variations/cros/cros_safe_seed_manager_unittest.cc rename to components/variations/cros_evaluate_seed/cros_safe_seed_manager_unittest.cc index 3bcd340..aded389 100644 --- a/components/variations/cros/cros_safe_seed_manager_unittest.cc +++ b/components/variations/cros_evaluate_seed/cros_safe_seed_manager_unittest.cc
@@ -2,13 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/variations/cros/cros_safe_seed_manager.h" +#include "components/variations/cros_evaluate_seed/cros_safe_seed_manager.h" #include "components/variations/service/safe_seed_manager_interface.h" #include "testing/gtest/include/gtest/gtest.h" -namespace variations { -namespace cros_early_boot { +namespace variations::cros_early_boot::evaluate_seed { TEST(CrosSafeSeedManagerTest, GetSeedType_RegularSeed) { CrOSSafeSeedManager safe_seed_manager(SeedType::kRegularSeed); @@ -22,5 +21,4 @@ EXPECT_EQ(SeedType::kSafeSeed, safe_seed_manager.GetSeedType()); } -} // namespace cros_early_boot -} // namespace variations +} // namespace variations::cros_early_boot::evaluate_seed
diff --git a/components/variations/cros/cros_variations_field_trial_creator.cc b/components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.cc similarity index 83% rename from components/variations/cros/cros_variations_field_trial_creator.cc rename to components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.cc index 03d8bdc5..a50523fb 100644 --- a/components/variations/cros/cros_variations_field_trial_creator.cc +++ b/components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.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 "components/variations/cros/cros_variations_field_trial_creator.h" +#include "components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.h" // Determining locale correctly is expensive in terms of code size due to the // libicu dependency (~400 KiB). Since it is not critical for early-boot @@ -11,7 +11,7 @@ class PrefService; -namespace variations { +namespace variations::cros_early_boot::evaluate_seed { CrOSVariationsFieldTrialCreator::CrOSVariationsFieldTrialCreator( VariationsServiceClient* client, @@ -28,4 +28,4 @@ return true; } -} // namespace variations +} // namespace variations::cros_early_boot::evaluate_seed
diff --git a/components/variations/cros/cros_variations_field_trial_creator.h b/components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.h similarity index 79% rename from components/variations/cros/cros_variations_field_trial_creator.h rename to components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.h index 542d9e2..7845f9ce 100644 --- a/components/variations/cros/cros_variations_field_trial_creator.h +++ b/components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef COMPONENTS_VARIATIONS_CROS_CROS_VARIATIONS_FIELD_TRIAL_CREATOR_H_ -#define COMPONENTS_VARIATIONS_CROS_CROS_VARIATIONS_FIELD_TRIAL_CREATOR_H_ +#ifndef COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_CROS_VARIATIONS_FIELD_TRIAL_CREATOR_H_ +#define COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_CROS_VARIATIONS_FIELD_TRIAL_CREATOR_H_ #include "components/variations/service/variations_field_trial_creator_base.h" @@ -12,6 +12,8 @@ class VariationsSeedStore; class VariationsServiceClient; +namespace cros_early_boot::evaluate_seed { + // Used to set up field trials for early-boot ChromeOS experiments based on // stored variations seed data. class CrOSVariationsFieldTrialCreator : public VariationsFieldTrialCreatorBase { @@ -40,6 +42,7 @@ void OverrideUIString(uint32_t hash, const std::u16string& str) override {} }; +} // namespace cros_early_boot::evaluate_seed } // namespace variations -#endif // COMPONENTS_VARIATIONS_CROS_CROS_VARIATIONS_FIELD_TRIAL_CREATOR_H_ +#endif // COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_CROS_VARIATIONS_FIELD_TRIAL_CREATOR_H_
diff --git a/components/variations/cros/evaluate_seed.cc b/components/variations/cros_evaluate_seed/evaluate_seed.cc similarity index 95% rename from components/variations/cros/evaluate_seed.cc rename to components/variations/cros_evaluate_seed/evaluate_seed.cc index b3c9c19..efd2cfd 100644 --- a/components/variations/cros/evaluate_seed.cc +++ b/components/variations/cros_evaluate_seed/evaluate_seed.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 "components/variations/cros/evaluate_seed.h" +#include "components/variations/cros_evaluate_seed/evaluate_seed.h" #include "base/check.h" #include "base/containers/flat_set.h" @@ -16,7 +16,7 @@ #include "components/variations/proto/study.pb.h" #include "components/variations/service/variations_field_trial_creator.h" -namespace variations::evaluate_seed { +namespace variations::cros_early_boot::evaluate_seed { namespace { constexpr char kSafeSeedSwitch[] = "use-safe-seed"; @@ -119,4 +119,4 @@ return EXIT_SUCCESS; } -} // namespace variations::evaluate_seed +} // namespace variations::cros_early_boot::evaluate_seed
diff --git a/components/variations/cros/evaluate_seed.h b/components/variations/cros_evaluate_seed/evaluate_seed.h similarity index 85% rename from components/variations/cros/evaluate_seed.h rename to components/variations/cros_evaluate_seed/evaluate_seed.h index 7a86de5..5c75e63 100644 --- a/components/variations/cros/evaluate_seed.h +++ b/components/variations/cros_evaluate_seed/evaluate_seed.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_H_ -#define COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_H_ +#ifndef COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_EVALUATE_SEED_H_ +#define COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_EVALUATE_SEED_H_ #include <memory> #include <string> @@ -16,7 +16,7 @@ #include "components/variations/service/variations_service_client.h" #include "third_party/abseil-cpp/absl/types/optional.h" -namespace variations::evaluate_seed { +namespace variations::cros_early_boot::evaluate_seed { class CrosVariationsServiceClient : public VariationsServiceClient { public: @@ -61,6 +61,6 @@ // Return values are standard for main methods (EXIT_SUCCESS / EXIT_FAILURE). int EvaluateSeedMain(FILE* stream); -} // namespace variations::evaluate_seed +} // namespace variations::cros_early_boot::evaluate_seed -#endif // COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_H_ +#endif // COMPONENTS_VARIATIONS_CROS_EVALUATE_SEED_EVALUATE_SEED_H_
diff --git a/components/variations/cros/evaluate_seed_main.cc b/components/variations/cros_evaluate_seed/evaluate_seed_main.cc similarity index 81% rename from components/variations/cros/evaluate_seed_main.cc rename to components/variations/cros_evaluate_seed/evaluate_seed_main.cc index bd2a9fa..287b052 100644 --- a/components/variations/cros/evaluate_seed_main.cc +++ b/components/variations/cros_evaluate_seed/evaluate_seed_main.cc
@@ -4,7 +4,7 @@ #include "base/at_exit.h" #include "base/threading/platform_thread.h" -#include "components/variations/cros/evaluate_seed.h" +#include "components/variations/cros_evaluate_seed/evaluate_seed.h" // evaluate_seed reads the seed data from Local State and prints computed state, // in a serialized format, to stdout. @@ -15,5 +15,5 @@ base::AtExitManager exit_manager; base::CommandLine::Init(argc, argv); - return variations::evaluate_seed::EvaluateSeedMain(stdin); + return variations::cros_early_boot::evaluate_seed::EvaluateSeedMain(stdin); }
diff --git a/components/variations/cros/evaluate_seed_unittest.cc b/components/variations/cros_evaluate_seed/evaluate_seed_unittest.cc similarity index 97% rename from components/variations/cros/evaluate_seed_unittest.cc rename to components/variations/cros_evaluate_seed/evaluate_seed_unittest.cc index c7d6cf5a..b24fa34 100644 --- a/components/variations/cros/evaluate_seed_unittest.cc +++ b/components/variations/cros_evaluate_seed/evaluate_seed_unittest.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 "components/variations/cros/evaluate_seed.h" +#include "components/variations/cros_evaluate_seed/evaluate_seed.h" #include "base/strings/strcat.h" #include "base/test/scoped_chromeos_version_info.h" @@ -13,7 +13,7 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -namespace variations::evaluate_seed { +namespace variations::cros_early_boot::evaluate_seed { MATCHER_P(EqualsProto, message, @@ -205,4 +205,4 @@ EXPECT_EQ(1, EvaluateSeedMain(nullptr)); } -} // namespace variations::evaluate_seed +} // namespace variations::cros_early_boot::evaluate_seed
diff --git a/components/version_ui/resources/BUILD.gn b/components/version_ui/resources/BUILD.gn index df7ed4d..de9da40c 100644 --- a/components/version_ui/resources/BUILD.gn +++ b/components/version_ui/resources/BUILD.gn
@@ -19,7 +19,7 @@ non_web_component_files = [ "about_version.ts" ] ts_definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ] - if (is_chromeos_ash) { + if (is_chromeos) { ts_definitions += [ "//tools/typescript/definitions/chromeos_info_private.d.ts" ] }
diff --git a/components/version_ui/resources/about_version.html b/components/version_ui/resources/about_version.html index d00bc990..cf46941 100644 --- a/components/version_ui/resources/about_version.html +++ b/components/version_ui/resources/about_version.html
@@ -21,7 +21,7 @@ <if expr="is_android or is_ios"> <link rel="stylesheet" href="about_version_mobile.css"> </if> -<if expr="chromeos_lacros or chromeos_ash"> +<if expr="is_chromeos"> <link rel="stylesheet" href="chrome://resources/css/os_header.css"> </if> @@ -29,7 +29,7 @@ </head> <body> -<if expr="chromeos_lacros or chromeos_ash"> +<if expr="is_chromeos"> <div class="os-link-container-container" id="os-link-container" hidden> <div class="os-link-container"> <span class="os-link-icon"></span> @@ -89,16 +89,23 @@ <span>$i18n{cl}</span> </td> </tr> -<if expr="not chromeos_ash and not chromeos_lacros"> +<if expr="not chromeos_ash"> <tr> <td class="label">$i18n{os_name}</td> <td class="version" id="os_type"> - <span>$i18n{os_type}</span> + <span id="copy-os-content"> + <span>$i18n{os_type}</span> <if expr="is_android"> - <span>$i18n{os_version}</span> + <span>$i18n{os_version}</span> </if> -<if expr="is_win or is_macosx"> - <span id="os_version"></span> +<if expr="chromeos_lacros or is_win or is_macosx"> + <span id="os_version"></span> +</if> + </span> +<if expr="chromeos_lacros"> + <button id="copy-os-content-to-clipboard" aria-label="$i18n{copy_label}"> + <div id="copy-to-clipboard-icon"></div> + </button> </if> </td> </tr> @@ -123,12 +130,11 @@ </td> </tr> </if> -<!-- TODO(crbug.com/1339120): Unify and reuse between Ash and Lacros --> -<if expr="chromeos_ash"> +<if expr="is_chromeos"> <tr> <td class="label">$i18n{platform}</td> - <td class="version" id="os_type"> - <span id="os_version"></span> + <td class="version" id="platform_type"> + <span id="platform_version"></span> </td> </tr> <tr> @@ -149,20 +155,6 @@ </td> </tr> </if> -<if expr="chromeos_lacros"> - <tr> - <td class="label">$i18n{os_name}</td> - <td class="version" id="os_type"> - <span id="copy-os-content"> - <span>$i18n{os_type}</span> - (Ash <span>$i18n{ash_chrome_version}</span>) - </span> - <button id="copy-os-content-to-clipboard" aria-label="$i18n{copy_label}"> - <div id="copy-to-clipboard-icon"></div> - </button> - </td> - </tr> -</if> <if expr="not is_ios"> <tr><td class="label">JavaScript</td> <td class="version" id="js_engine">
diff --git a/components/version_ui/resources/about_version.ts b/components/version_ui/resources/about_version.ts index cd575f5..944b04ca 100644 --- a/components/version_ui/resources/about_version.ts +++ b/components/version_ui/resources/about_version.ts
@@ -54,7 +54,7 @@ getRequiredElement('profile_path').textContent = profilePath; } -// <if expr="chromeos_ash or is_win"> +// <if expr="chromeos_lacros or is_win"> /** * Callback from the backend with the OS version to display. * @param osVersion The OS version to display. @@ -64,11 +64,19 @@ } // </if> -// <if expr="chromeos_ash"> +// <if expr="is_chromeos"> +/** + * Callback from the backend with the ChromeOS platform version to display. + * @param platformVersion The platform version to display. + */ +function returnPlatformVersion(platformVersion: string) { + getRequiredElement('platform_version').textContent = platformVersion; +} + /** * Callback from the backend with the firmware version to display. */ -function returnOsFirmwareVersion(firmwareVersion: string) { +function returnFirmwareVersion(firmwareVersion: string) { getRequiredElement('firmware_version').textContent = firmwareVersion; } @@ -94,9 +102,7 @@ getRequiredElement('customization_id').textContent = response['customizationId']; } -// </if> -// <if expr="is_chromeos"> /** * Callback from the backend to inform if Lacros is enabled or not. * @param enabled True if it is enabled. @@ -132,17 +138,19 @@ /* All the work we do onload. */ function initialize() { - // <if expr="chromeos_ash or is_win"> + // <if expr="chromeos_lacros or is_win"> addWebUiListener('return-os-version', returnOsVersion); // </if> - // <if expr="chromeos_ash"> - addWebUiListener('return-os-firmware-version', returnOsFirmwareVersion); + // <if expr="is_chromeos"> + addWebUiListener('return-platform-version', returnPlatformVersion); + addWebUiListener('return-firmware-version', returnFirmwareVersion); addWebUiListener( 'return-arc-and-arc-android-sdk-versions', returnArcAndArcAndroidSdkVersions); - // </if> - // <if expr="is_chromeos"> addWebUiListener('return-lacros-enabled', returnLacrosEnabled); + getRequiredElement('arc_holder').hidden = true; + chrome.chromeosInfoPrivate.get(['customizationId']) + .then(returnCustomizationId); // </if> chrome.send('requestVersionInfo'); @@ -151,12 +159,6 @@ .then(handleVariationInfo); sendWithPromise('requestPathInfo').then(handlePathInfo); - // <if expr="chromeos_ash"> - getRequiredElement('arc_holder').hidden = true; - chrome.chromeosInfoPrivate.get(['customizationId']) - .then(returnCustomizationId); - // </if> - if (getRequiredElement('variations-seed').textContent !== '') { getRequiredElement('variations-seed-section').hidden = false; }
diff --git a/components/version_ui/version_ui_constants.cc b/components/version_ui/version_ui_constants.cc index 48efbeb..91d1a6ce 100644 --- a/components/version_ui/version_ui_constants.cc +++ b/components/version_ui/version_ui_constants.cc
@@ -22,7 +22,7 @@ // Strings. const char kApplicationLabel[] = "application_label"; -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) const char kARC[] = "arc_label"; #endif const char kCL[] = "cl"; @@ -33,19 +33,16 @@ const char kUpdateCohortName[] = "update_cohort_name"; #endif const char kCopyright[] = "copyright"; -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) const char kCustomizationId[] = "customization_id"; #endif #if !BUILDFLAG(IS_IOS) const char kExecutablePath[] = "executable_path"; const char kExecutablePathName[] = "executable_path_name"; #endif -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) const char kFirmwareVersion[] = "firmware_version"; #endif -#if BUILDFLAG(IS_CHROMEOS_LACROS) -const char kAshChromeVersion[] = "ash_chrome_version"; -#endif // BUILDFLAG(IS_CHROMEOS_LACROS) #if !BUILDFLAG(IS_IOS) const char kJSEngine[] = "js_engine"; const char kJSVersion[] = "js_version"; @@ -66,7 +63,7 @@ const char kGmsName[] = "gms_name"; const char kGmsVersion[] = "gms_version"; #endif -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) const char kPlatform[] = "platform"; #endif #if !BUILDFLAG(IS_IOS)
diff --git a/components/version_ui/version_ui_constants.h b/components/version_ui/version_ui_constants.h index f3309b79..61a18236 100644 --- a/components/version_ui/version_ui_constants.h +++ b/components/version_ui/version_ui_constants.h
@@ -24,7 +24,7 @@ // Strings. // Must match the constants used in the resource files. extern const char kApplicationLabel[]; -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) extern const char kARC[]; #endif extern const char kCL[]; @@ -35,19 +35,16 @@ extern const char kUpdateCohortName[]; #endif extern const char kCopyright[]; -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) extern const char kCustomizationId[]; #endif #if !BUILDFLAG(IS_IOS) extern const char kExecutablePath[]; extern const char kExecutablePathName[]; #endif -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) extern const char kFirmwareVersion[]; #endif -#if BUILDFLAG(IS_CHROMEOS_LACROS) -extern const char kAshChromeVersion[]; -#endif #if !BUILDFLAG(IS_IOS) extern const char kJSEngine[]; extern const char kJSVersion[]; @@ -68,7 +65,7 @@ extern const char kGmsName[]; extern const char kGmsVersion[]; #endif -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) extern const char kPlatform[]; #endif #if !BUILDFLAG(IS_IOS)
diff --git a/components/version_ui_strings.grdp b/components/version_ui_strings.grdp index c7b40ac..97d5fbc9 100644 --- a/components/version_ui_strings.grdp +++ b/components/version_ui_strings.grdp
@@ -53,7 +53,7 @@ <message name="IDS_VERSION_UI_COMMAND_LINE" desc="label for the command line on the about:version page"> Command Line </message> - <if expr="chromeos_ash"> + <if expr="is_chromeos"> <message name="IDS_VERSION_UI_BUILD_DATE" desc="label for build date on the about:version page"> Build Date </message>
diff --git a/components/viz/service/display_embedder/compositor_gpu_thread.cc b/components/viz/service/display_embedder/compositor_gpu_thread.cc index 835bea3..dae2c9c 100644 --- a/components/viz/service/display_embedder/compositor_gpu_thread.cc +++ b/components/viz/service/display_embedder/compositor_gpu_thread.cc
@@ -207,7 +207,7 @@ // Initialize Skia. if (!shared_context_state->InitializeSkia( gpu_preferences, workarounds, gpu_channel_manager_->gr_shader_cache(), - /*use_shader_cache_shm_count=*/nullptr, + gpu_channel_manager_->use_shader_cache_shm_count(), /*progress_reporter=*/nullptr)) { LOG(ERROR) << "Failed to Initialize Skia for DrDC SharedContextState"; }
diff --git a/components/viz/service/frame_sinks/external_begin_frame_source_ios.mm b/components/viz/service/frame_sinks/external_begin_frame_source_ios.mm index 6976d42..3f9ed5e 100644 --- a/components/viz/service/frame_sinks/external_begin_frame_source_ios.mm +++ b/components/viz/service/frame_sinks/external_begin_frame_source_ios.mm
@@ -8,8 +8,8 @@ #import <QuartzCore/QuartzCore.h> #import <UIKit/UIKit.h> +#include "base/apple/mach_logging.h" #include "base/logging.h" -#include "base/mac/mach_logging.h" #include "base/numerics/checked_math.h" #include "components/viz/common/frame_sinks/begin_frame_args.h"
diff --git a/components/wifi/wifi_test.cc b/components/wifi/wifi_test.cc index a741ee3..3ae9847 100644 --- a/components/wifi/wifi_test.cc +++ b/components/wifi/wifi_test.cc
@@ -27,7 +27,7 @@ #include "components/wifi/wifi_service.h" #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif namespace wifi { @@ -73,7 +73,7 @@ #if BUILDFLAG(IS_APPLE) // Without this there will be a mem leak on osx. - base::mac::ScopedNSAutoreleasePool scoped_pool_; + base::apple::ScopedNSAutoreleasePool scoped_pool_; #endif std::unique_ptr<WiFiService> wifi_service_;
diff --git a/content/app/content_main.cc b/content/app/content_main.cc index 6c672cd..a3c00c9 100644 --- a/content/app/content_main.cc +++ b/content/app/content_main.cc
@@ -67,7 +67,7 @@ #endif #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "content/app/mac_init.h" #endif @@ -186,7 +186,7 @@ #endif int exit_code = -1; #if BUILDFLAG(IS_MAC) - std::unique_ptr<base::mac::ScopedNSAutoreleasePool> autorelease_pool; + std::unique_ptr<base::apple::ScopedNSAutoreleasePool> autorelease_pool; #endif // A flag to indicate whether Main() has been called before. On Android, we @@ -277,7 +277,7 @@ // loop, but we don't want to leave them hanging around until the app quits. // Each "main" needs to flush this pool right before it goes into its main // event loop to get rid of the cruft. - autorelease_pool = std::make_unique<base::mac::ScopedNSAutoreleasePool>(); + autorelease_pool = std::make_unique<base::apple::ScopedNSAutoreleasePool>(); params.autorelease_pool = autorelease_pool.get(); InitializeMac(); #endif
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn index 032b82b8d..4f74e15b 100644 --- a/content/browser/BUILD.gn +++ b/content/browser/BUILD.gn
@@ -863,6 +863,8 @@ "devtools/service_worker_devtools_manager.h", "devtools/shared_storage_worklet_devtools_agent_host.cc", "devtools/shared_storage_worklet_devtools_agent_host.h", + "devtools/shared_storage_worklet_devtools_manager.cc", + "devtools/shared_storage_worklet_devtools_manager.h", "devtools/shared_worker_devtools_agent_host.cc", "devtools/shared_worker_devtools_agent_host.h", "devtools/shared_worker_devtools_manager.cc",
diff --git a/content/browser/attribution_reporting/attribution_config.h b/content/browser/attribution_reporting/attribution_config.h index 72fa14a..2da6804 100644 --- a/content/browser/attribution_reporting/attribution_config.h +++ b/content/browser/attribution_reporting/attribution_config.h
@@ -111,7 +111,7 @@ // Default constants for max info gain in bits per source type. // Rounded up to nearest e-5 digit. static constexpr double kDefaultMaxNavigationInfoGain = 11.46173; - static constexpr double kDefaultMaxEventInfoGain = 1.58494; + static constexpr double kDefaultMaxEventInfoGain = 6.5; // Controls the max number bits of information that can be associated with // a single a source.
diff --git a/content/browser/attribution_reporting/attribution_storage_unittest.cc b/content/browser/attribution_reporting/attribution_storage_unittest.cc index ec4befb6..6e012f8 100644 --- a/content/browser/attribution_reporting/attribution_storage_unittest.cc +++ b/content/browser/attribution_reporting/attribution_storage_unittest.cc
@@ -734,10 +734,15 @@ } TEST_F(AttributionStorageTest, ExceedsChannelCapacity) { - delegate()->set_channel_capacity(2); + delegate()->set_channel_capacity(6.51); - auto source = SourceBuilder().SetSourceType(SourceType::kEvent).Build(); - EXPECT_EQ(storage()->StoreSource(source).status, + EXPECT_EQ(storage()->StoreSource(SourceBuilder().Build()).status, + StorableSource::Result::kSuccess); + + EXPECT_EQ(storage() + ->StoreSource( + SourceBuilder().SetSourceType(SourceType::kEvent).Build()) + .status, StorableSource::Result::kExceedsMaxChannelCapacity); }
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc index c9c47ba7..2a00c12 100644 --- a/content/browser/browser_main_loop.cc +++ b/content/browser/browser_main_loop.cc
@@ -180,7 +180,7 @@ #endif #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "content/browser/renderer_host/browser_compositor_view_mac.h" #include "content/browser/theme_helper_mac.h" #include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
diff --git a/content/browser/cache_storage/cache_storage_cache.cc b/content/browser/cache_storage/cache_storage_cache.cc index 00b04c0..9c39e149 100644 --- a/content/browser/cache_storage/cache_storage_cache.cc +++ b/content/browser/cache_storage/cache_storage_cache.cc
@@ -280,17 +280,17 @@ return true; } -// Check a batch operation list for duplicate entries. A StackVector -// must be passed to store any resulting duplicate URL strings. Returns -// true if any duplicates were found. -bool FindDuplicateOperations( - const std::vector<blink::mojom::BatchOperationPtr>& operations, - std::vector<std::string>* duplicate_url_list_out) { +// Checks a batch operation list for duplicate entries. Returns any duplicate +// URL strings that were found. If the return value is empty, then there were no +// duplicates. +std::vector<std::string> FindDuplicateOperations( + const std::vector<blink::mojom::BatchOperationPtr>& operations) { using blink::mojom::BatchOperation; - DCHECK(duplicate_url_list_out); + + std::vector<std::string> duplicate_url_list; if (operations.size() < 2) { - return false; + return duplicate_url_list; } // Create a temporary sorted vector of the operations to support quickly @@ -330,8 +330,8 @@ // If this entry already matches a duplicate we found, then just skip // ahead to find any remaining duplicates. - if (!duplicate_url_list_out->empty() && - outer_op->request->url.spec() == duplicate_url_list_out->back()) { + if (!duplicate_url_list.empty() && + outer_op->request->url.spec() == duplicate_url_list.back()) { continue; } @@ -354,13 +354,13 @@ VaryMatches(outer_op->request->headers, inner_op->request->headers, outer_op->response->response_type, outer_op->response->headers)) { - duplicate_url_list_out->push_back(inner_op->request->url.spec()); + duplicate_url_list.push_back(inner_op->request->url.spec()); break; } } } - return !duplicate_url_list_out->empty(); + return duplicate_url_list; } GURL RemoveQueryParam(const GURL& url) { @@ -770,11 +770,13 @@ // "If the result of running Query Cache with operation’s request, // operation’s options, and addedItems is not empty, throw an // InvalidStateError DOMException." - std::vector<std::string> duplicate_url_list; - if (FindDuplicateOperations(operations, &duplicate_url_list)) { + + if (const auto duplicate_url_list = FindDuplicateOperations(operations); + !duplicate_url_list.empty()) { // If we found any duplicates we need to at least warn the user. Format // the URL list into a comma-separated list. - std::string url_list_string = base::JoinString(duplicate_url_list, ", "); + const std::string url_list_string = + base::JoinString(duplicate_url_list, ", "); // Place the duplicate list into an error message. message.emplace(
diff --git a/content/browser/child_process_task_port_provider_mac.cc b/content/browser/child_process_task_port_provider_mac.cc index 5e7f4fa0..73b2aaa 100644 --- a/content/browser/child_process_task_port_provider_mac.cc +++ b/content/browser/child_process_task_port_provider_mac.cc
@@ -4,12 +4,12 @@ #include "content/browser/child_process_task_port_provider_mac.h" +#include "base/apple/mach_logging.h" #include "base/containers/cxx20_erase.h" #include "base/debug/crash_logging.h" #include "base/functional/bind.h" #include "base/logging.h" #include "base/mac/foundation_util.h" -#include "base/mac/mach_logging.h" #include "base/no_destructor.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" @@ -54,7 +54,7 @@ return; } - CHECK(base::mac::CreateMachPort(¬ification_port_, nullptr)); + CHECK(base::apple::CreateMachPort(¬ification_port_, nullptr)); const std::string dispatch_name = base::StringPrintf( "%s.ChildProcessTaskPortProvider.%p", base::mac::BaseBundleID(), this); @@ -98,15 +98,15 @@ DLOG(ERROR) << "Invalid handle received as task port for pid " << pid; return; } - base::mac::ScopedMachSendRight port = task_port.TakeMachSendRight(); + base::apple::ScopedMachSendRight port = task_port.TakeMachSendRight(); // Request a notification from the kernel for when the port becomes a dead // name, indicating that the process has died. - base::mac::ScopedMachSendRight previous; + base::apple::ScopedMachSendRight previous; kern_return_t kr = mach_port_request_notification( mach_task_self(), port.get(), MACH_NOTIFY_DEAD_NAME, 0, notification_port_.get(), MACH_MSG_TYPE_MAKE_SEND_ONCE, - base::mac::ScopedMachSendRight::Receiver(previous).get()); + base::apple::ScopedMachSendRight::Receiver(previous).get()); if (kr != KERN_SUCCESS) { // If the argument was invalid, the process is likely already dead. MACH_DVLOG(1, kr) << "mach_port_request_notification"; @@ -154,7 +154,7 @@ return; // Release the DEAD_NAME right. - base::mac::ScopedMachSendRight dead_port(notification.not_port); + base::apple::ScopedMachSendRight dead_port(notification.not_port); base::AutoLock lock(lock_); base::EraseIf(pid_to_task_port_, [&dead_port](const auto& pair) {
diff --git a/content/browser/child_process_task_port_provider_mac.h b/content/browser/child_process_task_port_provider_mac.h index 672272e..a56d33b 100644 --- a/content/browser/child_process_task_port_provider_mac.h +++ b/content/browser/child_process_task_port_provider_mac.h
@@ -8,7 +8,7 @@ #include <map> #include "base/apple/dispatch_source_mach.h" -#include "base/mac/scoped_mach_port.h" +#include "base/apple/scoped_mach_port.h" #include "base/no_destructor.h" #include "base/process/port_provider_mac.h" #include "base/process/process_handle.h" @@ -73,13 +73,13 @@ // Maps a PID to the corresponding task port. using PidToTaskPortMap = - std::map<base::ProcessHandle, base::mac::ScopedMachSendRight>; + std::map<base::ProcessHandle, base::apple::ScopedMachSendRight>; PidToTaskPortMap pid_to_task_port_; // A Mach port that is used to register for dead name notifications from // the kernel. All the ports in |pid_to_task_port_| have a notification set // up to send to this port. - base::mac::ScopedMachReceiveRight notification_port_; + base::apple::ScopedMachReceiveRight notification_port_; // Dispatch source for |notification_port_|. std::unique_ptr<base::DispatchSourceMach> notification_source_;
diff --git a/content/browser/child_process_task_port_provider_mac_unittest.cc b/content/browser/child_process_task_port_provider_mac_unittest.cc index ab99b6d..7e2a97f 100644 --- a/content/browser/child_process_task_port_provider_mac_unittest.cc +++ b/content/browser/child_process_task_port_provider_mac_unittest.cc
@@ -8,9 +8,9 @@ #include <vector> +#include "base/apple/scoped_mach_port.h" #include "base/clang_profiling_buildflags.h" #include "base/functional/callback.h" -#include "base/mac/scoped_mach_port.h" #include "base/task/sequenced_task_runner.h" #include "base/task/thread_pool.h" #include "base/test/task_environment.h" @@ -122,9 +122,9 @@ EXPECT_EQ(kMachPortNull, provider()->TaskForPid(99)); // Create a fake task port for the fake process. - base::mac::ScopedMachReceiveRight receive_right; - base::mac::ScopedMachSendRight send_right; - ASSERT_TRUE(base::mac::CreateMachPort(&receive_right, &send_right)); + base::apple::ScopedMachReceiveRight receive_right; + base::apple::ScopedMachSendRight send_right; + ASSERT_TRUE(base::apple::CreateMachPort(&receive_right, &send_right)); EXPECT_EQ(1u, GetSendRightRefCount(send_right.get())); EXPECT_EQ(0u, GetDeadNameRefCount(send_right.get())); @@ -135,7 +135,7 @@ .WillOnce(WithArgs<0>( [&send_right](mojom::ChildProcess::GetTaskPortCallback callback) { std::move(callback).Run(mojo::PlatformHandle( - base::mac::RetainMachSendRight(send_right.get()))); + base::apple::RetainMachSendRight(send_right.get()))); })); provider()->OnChildProcessLaunched(99, &child_process); @@ -169,9 +169,9 @@ EXPECT_EQ(kMachPortNull, provider()->TaskForPid(6)); // Create a fake task port for the fake process. - base::mac::ScopedMachReceiveRight receive_right; - base::mac::ScopedMachSendRight send_right; - ASSERT_TRUE(base::mac::CreateMachPort(&receive_right, &send_right)); + base::apple::ScopedMachReceiveRight receive_right; + base::apple::ScopedMachSendRight send_right; + ASSERT_TRUE(base::apple::CreateMachPort(&receive_right, &send_right)); scoped_refptr<base::SequencedTaskRunner> task_runner = base::ThreadPool::CreateSequencedTaskRunner({}); @@ -182,12 +182,12 @@ WithArgs<0>([&task_runner, &receive_right, &send_right]( mojom::ChildProcess::GetTaskPortCallback callback) { mojo::PlatformHandle mach_handle( - base::mac::RetainMachSendRight(send_right.get())); + base::apple::RetainMachSendRight(send_right.get())); // Destroy the receive right. task_runner->PostTask( FROM_HERE, - base::BindOnce(&base::mac::ScopedMachReceiveRight::reset, + base::BindOnce(&base::apple::ScopedMachReceiveRight::reset, base::Unretained(&receive_right), kMachPortNull)); // And then return a send right to the now-dead name. @@ -199,9 +199,9 @@ provider()->OnChildProcessLaunched(6, &child_process); // Create a second fake process. - base::mac::ScopedMachReceiveRight receive_right2; - base::mac::ScopedMachSendRight send_right2; - ASSERT_TRUE(base::mac::CreateMachPort(&receive_right2, &send_right2)); + base::apple::ScopedMachReceiveRight receive_right2; + base::apple::ScopedMachSendRight send_right2; + ASSERT_TRUE(base::apple::CreateMachPort(&receive_right2, &send_right2)); MockChildProcess child_contol2; EXPECT_CALL(child_contol2, GetTaskPort(_)) @@ -213,7 +213,7 @@ base::BindOnce( std::move(callback), mojo::PlatformHandle( - base::mac::RetainMachSendRight(send_right2.get())))); + base::apple::RetainMachSendRight(send_right2.get())))); })); provider()->OnChildProcessLaunched(123, &child_contol2); @@ -239,9 +239,9 @@ EXPECT_EQ(kMachPortNull, provider()->TaskForPid(42)); // Create a fake task port for the fake process. - base::mac::ScopedMachReceiveRight receive_right; - base::mac::ScopedMachSendRight send_right; - ASSERT_TRUE(base::mac::CreateMachPort(&receive_right, &send_right)); + base::apple::ScopedMachReceiveRight receive_right; + base::apple::ScopedMachSendRight send_right; + ASSERT_TRUE(base::apple::CreateMachPort(&receive_right, &send_right)); EXPECT_EQ(1u, GetSendRightRefCount(send_right.get())); EXPECT_EQ(0u, GetDeadNameRefCount(send_right.get())); @@ -253,7 +253,7 @@ .WillRepeatedly(WithArgs<0>( [&receive_right](mojom::ChildProcess::GetTaskPortCallback callback) { std::move(callback).Run(mojo::PlatformHandle( - base::mac::RetainMachSendRight(receive_right.get()))); + base::apple::RetainMachSendRight(receive_right.get()))); })); provider()->OnChildProcessLaunched(42, &child_process); @@ -274,9 +274,9 @@ EXPECT_EQ(receive_right.get(), provider()->TaskForPid(42)); // Now simulate PID reuse by replacing the task port with a new one. - base::mac::ScopedMachReceiveRight receive_right2; - base::mac::ScopedMachSendRight send_right2; - ASSERT_TRUE(base::mac::CreateMachPort(&receive_right2, &send_right2)); + base::apple::ScopedMachReceiveRight receive_right2; + base::apple::ScopedMachSendRight send_right2; + ASSERT_TRUE(base::apple::CreateMachPort(&receive_right2, &send_right2)); EXPECT_EQ(1u, GetSendRightRefCount(send_right2.get())); MockChildProcess child_process2; @@ -284,7 +284,7 @@ .WillOnce( [&send_right2](mojom::ChildProcess::GetTaskPortCallback callback) { std::move(callback).Run(mojo::PlatformHandle( - base::mac::RetainMachSendRight(send_right2.get()))); + base::apple::RetainMachSendRight(send_right2.get()))); }); provider()->OnChildProcessLaunched(42, &child_process2);
diff --git a/content/browser/devtools/browser_devtools_agent_host.cc b/content/browser/devtools/browser_devtools_agent_host.cc index e4bea03..06bb7b75 100644 --- a/content/browser/devtools/browser_devtools_agent_host.cc +++ b/content/browser/devtools/browser_devtools_agent_host.cc
@@ -133,6 +133,9 @@ if (host->GetType() == DevToolsAgentHost::kTypeSharedWorker) { return true; } + if (host->GetType() == DevToolsAgentHost::kTypeSharedStorageWorklet) { + return true; + } if (host->GetType() == DevToolsAgentHost::kTypeTab) { return true; }
diff --git a/content/browser/devtools/devtools_agent_host_impl.cc b/content/browser/devtools/devtools_agent_host_impl.cc index af45864..90a72a6 100644 --- a/content/browser/devtools/devtools_agent_host_impl.cc +++ b/content/browser/devtools/devtools_agent_host_impl.cc
@@ -25,6 +25,7 @@ #include "content/browser/devtools/render_frame_devtools_agent_host.h" #include "content/browser/devtools/service_worker_devtools_agent_host.h" #include "content/browser/devtools/service_worker_devtools_manager.h" +#include "content/browser/devtools/shared_storage_worklet_devtools_manager.h" #include "content/browser/devtools/shared_worker_devtools_agent_host.h" #include "content/browser/devtools/shared_worker_devtools_manager.h" #include "content/browser/devtools/web_contents_devtools_agent_host.h" @@ -173,6 +174,8 @@ for (const auto& host : service_list) result.push_back(host); + SharedStorageWorkletDevToolsManager::GetInstance()->AddAllAgentHosts(&result); + // TODO(dgozman): we should add dedicated workers here, but clients are not // ready. RenderFrameDevToolsAgentHost::AddAllAgentHosts(&result);
diff --git a/content/browser/devtools/shared_storage_worklet_devtools_manager.cc b/content/browser/devtools/shared_storage_worklet_devtools_manager.cc new file mode 100644 index 0000000..19a6edd8 --- /dev/null +++ b/content/browser/devtools/shared_storage_worklet_devtools_manager.cc
@@ -0,0 +1,65 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/devtools/shared_storage_worklet_devtools_manager.h" + +#include "base/ranges/algorithm.h" +#include "content/browser/devtools/shared_storage_worklet_devtools_agent_host.h" +#include "content/browser/shared_storage/shared_storage_worklet_host.h" +#include "content/public/browser/browser_thread.h" + +namespace content { + +// static +SharedStorageWorkletDevToolsManager* +SharedStorageWorkletDevToolsManager::GetInstance() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + return base::Singleton<SharedStorageWorkletDevToolsManager>::get(); +} + +void SharedStorageWorkletDevToolsManager::AddAllAgentHosts( + std::vector<scoped_refptr<DevToolsAgentHost>>* result) { + for (auto& it : hosts_) { + result->push_back(it.second.get()); + } +} + +void SharedStorageWorkletDevToolsManager::WorkletCreated( + SharedStorageWorkletHost& worklet_host, + const base::UnguessableToken& devtools_worklet_token) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + CHECK(hosts_.find(&worklet_host) == hosts_.end()); + + hosts_[&worklet_host] = MakeRefCounted<SharedStorageWorkletDevToolsAgentHost>( + worklet_host, devtools_worklet_token); +} + +void SharedStorageWorkletDevToolsManager::WorkletReadyForInspection( + SharedStorageWorkletHost& worklet_host, + mojo::PendingRemote<blink::mojom::DevToolsAgent> agent_remote, + mojo::PendingReceiver<blink::mojom::DevToolsAgentHost> + agent_host_receiver) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + hosts_.at(&worklet_host) + ->WorkletReadyForInspection(std::move(agent_remote), + std::move(agent_host_receiver)); +} + +void SharedStorageWorkletDevToolsManager::WorkletDestroyed( + SharedStorageWorkletHost& worklet_host) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + auto it = hosts_.find(&worklet_host); + CHECK(it != hosts_.end()); + + it->second->WorkletDestroyed(); + hosts_.erase(it); +} + +SharedStorageWorkletDevToolsManager::SharedStorageWorkletDevToolsManager() = + default; +SharedStorageWorkletDevToolsManager::~SharedStorageWorkletDevToolsManager() = + default; + +} // namespace content
diff --git a/content/browser/devtools/shared_storage_worklet_devtools_manager.h b/content/browser/devtools/shared_storage_worklet_devtools_manager.h new file mode 100644 index 0000000..6c73531 --- /dev/null +++ b/content/browser/devtools/shared_storage_worklet_devtools_manager.h
@@ -0,0 +1,59 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_DEVTOOLS_SHARED_STORAGE_WORKLET_DEVTOOLS_MANAGER_H_ +#define CONTENT_BROWSER_DEVTOOLS_SHARED_STORAGE_WORKLET_DEVTOOLS_MANAGER_H_ + +#include <map> + +#include "base/containers/flat_set.h" +#include "base/gtest_prod_util.h" +#include "base/memory/singleton.h" +#include "base/unguessable_token.h" +#include "content/public/browser/devtools_agent_host.h" +#include "third_party/blink/public/mojom/devtools/devtools_agent.mojom.h" + +namespace content { + +class SharedStorageWorkletDevToolsAgentHost; +class SharedStorageWorkletHost; + +// Manages `SharedStorageWorkletDevToolsAgentHost`s for Shared Storage Worklets. +class SharedStorageWorkletDevToolsManager { + public: + // Returns the SharedStorageWorkletDevToolsManager singleton. + static SharedStorageWorkletDevToolsManager* GetInstance(); + + SharedStorageWorkletDevToolsManager( + const SharedStorageWorkletDevToolsManager&) = delete; + SharedStorageWorkletDevToolsManager& operator=( + const SharedStorageWorkletDevToolsManager&) = delete; + + void AddAllAgentHosts(std::vector<scoped_refptr<DevToolsAgentHost>>* result); + + void WorkletCreated(SharedStorageWorkletHost& worklet_host, + const base::UnguessableToken& devtools_worklet_token); + void WorkletReadyForInspection( + SharedStorageWorkletHost& worklet_host, + mojo::PendingRemote<blink::mojom::DevToolsAgent> agent_remote, + mojo::PendingReceiver<blink::mojom::DevToolsAgentHost> + agent_host_receiver); + void WorkletDestroyed(SharedStorageWorkletHost& worklet_host); + + private: + friend struct base::DefaultSingletonTraits< + SharedStorageWorkletDevToolsManager>; + + SharedStorageWorkletDevToolsManager(); + ~SharedStorageWorkletDevToolsManager(); + + // We retatin agent hosts as long as the shared storage worklet is alive. + std::map<SharedStorageWorkletHost*, + scoped_refptr<SharedStorageWorkletDevToolsAgentHost>> + hosts_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_SHARED_STORAGE_WORKLET_DEVTOOLS_MANAGER_H_
diff --git a/content/browser/memory/swap_metrics_driver_impl_mac.cc b/content/browser/memory/swap_metrics_driver_impl_mac.cc index d692a31..519d4184 100644 --- a/content/browser/memory/swap_metrics_driver_impl_mac.cc +++ b/content/browser/memory/swap_metrics_driver_impl_mac.cc
@@ -8,7 +8,7 @@ #include <mach/mach_vm.h> #include <memory> -#include "base/mac/mach_logging.h" +#include "base/apple/mach_logging.h" #include "base/memory/ptr_util.h" #include "base/time/time.h"
diff --git a/content/browser/memory/swap_metrics_driver_impl_mac.h b/content/browser/memory/swap_metrics_driver_impl_mac.h index 8a8a5495..6c27410 100644 --- a/content/browser/memory/swap_metrics_driver_impl_mac.h +++ b/content/browser/memory/swap_metrics_driver_impl_mac.h
@@ -9,7 +9,7 @@ #include <memory> -#include "base/mac/scoped_mach_port.h" +#include "base/apple/scoped_mach_port.h" #include "base/time/time.h" namespace content { @@ -25,7 +25,7 @@ base::TimeDelta interval) override; private: - base::mac::ScopedMachSendRight host_; + base::apple::ScopedMachSendRight host_; uint64_t last_swapins_ = 0; uint64_t last_swapouts_ = 0;
diff --git a/content/browser/preloading/prefetch/prefetch_container.cc b/content/browser/preloading/prefetch/prefetch_container.cc index d605618..727f99ac 100644 --- a/content/browser/preloading/prefetch/prefetch_container.cc +++ b/content/browser/preloading/prefetch/prefetch_container.cc
@@ -695,6 +695,19 @@ return streaming_loaders_.back().get(); } +const PrefetchResponseReader* PrefetchContainer::GetNonRedirectResponseReader() + const { + if (redirect_chain_.empty()) { + return nullptr; + } + if (!redirect_chain_.back()->response_reader_->GetHead()) { + // Either the last PrefetchResponseReader is for a redirect response, or for + // a final response not yet receiving its header. + return nullptr; + } + return redirect_chain_.back()->response_reader_.get(); +} + PrefetchResponseReader::RequestHandler PrefetchContainer::Reader::CreateRequestHandler() { return GetPrefetchContainer()->CreateRequestHandlerInternal(*this); @@ -744,7 +757,6 @@ } void PrefetchContainer::ResetAllStreamingURLLoaders() { - CHECK(!streaming_loaders_.empty()); for (auto& streaming_loader : streaming_loaders_) { // The PrefetchStreamingURLLoader and PrefetchResponseReader can be deleted // in one of its callbacks, so instead of deleting it immediately, it is @@ -807,13 +819,13 @@ UMA_HISTOGRAM_COUNTS_100("PrefetchProxy.Prefetch.RedirectChainSize", redirect_chain_.size()); - if (streaming_loaders_.empty()) { + if (!GetNonRedirectResponseReader()) { return; } UpdatePrefetchRequestMetrics( - GetLastStreamingURLLoader()->GetCompletionStatus(), - GetLastStreamingURLLoader()->GetHead()); + GetNonRedirectResponseReader()->GetCompletionStatus(), + GetNonRedirectResponseReader()->GetHead()); UpdateServingPageMetrics(); } @@ -839,9 +851,12 @@ bool PrefetchContainer::ShouldBlockUntilHeadReceived() const { // Can only block until head if the request has been started using a streaming - // URL loader and head hasn't been received yet. - if (streaming_loaders_.empty() || GetLastStreamingURLLoader()->GetHead() || - GetLastStreamingURLLoader()->Failed()) { + // URL loader... + if (streaming_loaders_.empty() || GetLastStreamingURLLoader()->Failed()) { + return false; + } + // ... and head hasn't been received yet. + if (GetNonRedirectResponseReader()) { return false; } return PrefetchShouldBlockUntilHead(prefetch_type_.GetEagerness()); @@ -861,10 +876,17 @@ bool PrefetchContainer::IsPrefetchServable( base::TimeDelta cacheable_duration) const { - // Whether or not the response (either full or partial) from the streaming URL - // loader is servable. - return !streaming_loaders_.empty() && - GetLastStreamingURLLoader()->Servable(cacheable_duration); + // Currently `CreateRequestHandler()` requires the corresponding streaming + // loader. + // TODO(crbug.com/1449360): Remove this requirement. + if (streaming_loaders_.empty()) { + return false; + } + + // Whether or not the non-redirect response (either fully or partially + // received body) is servable. + return GetNonRedirectResponseReader() && + GetNonRedirectResponseReader()->Servable(cacheable_duration); } bool PrefetchContainer::Reader::DoesCurrentURLToServeMatch( @@ -905,8 +927,9 @@ } const network::mojom::URLResponseHead* PrefetchContainer::GetHead() { - PrefetchStreamingURLLoader* streaming_loader = GetLastStreamingURLLoader(); - return streaming_loader ? streaming_loader->GetHead() : nullptr; + return GetNonRedirectResponseReader() + ? GetNonRedirectResponseReader()->GetHead() + : nullptr; } void PrefetchContainer::SetServingPageMetrics(
diff --git a/content/browser/preloading/prefetch/prefetch_container.h b/content/browser/preloading/prefetch/prefetch_container.h index dde8faf6..7c0d41de 100644 --- a/content/browser/preloading/prefetch/prefetch_container.h +++ b/content/browser/preloading/prefetch/prefetch_container.h
@@ -201,10 +201,19 @@ bool HasStreamingURLLoadersForTest() const; - // Returns the last |PrefetchStreamingURLLoader| from |streaming_loaders_|. - // This URL loader should be used when fetching the prefetch. + // Returns the last |PrefetchStreamingURLLoader| from |streaming_loaders_|, + // i.e. the URL loader being used for prefetching the current redirect hop. + // This method should be used during prefetching and shouldn't be called for + // serving purpose. + // + // TODO(https://crbug.com/1449360): Migrate callers (e.g. to + // GetNonRedirectResponseReader()) that don't meet this criteria. PrefetchStreamingURLLoader* GetLastStreamingURLLoader() const; + // Returns the PrefetchResponseReader corresponding to the last non-redirect + // response, if already received its head, or otherwise nullptr. + const PrefetchResponseReader* GetNonRedirectResponseReader() const; + // Clears all |PrefetchStreamingURLLoader|s and |PrefetchResponseReader|s from // |streaming_loaders_|. void ResetAllStreamingURLLoaders();
diff --git a/content/browser/preloading/prefetch/prefetch_container_unittest.cc b/content/browser/preloading/prefetch/prefetch_container_unittest.cc index 271a6ba4..6a28c2d9e 100644 --- a/content/browser/preloading/prefetch/prefetch_container_unittest.cc +++ b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
@@ -18,6 +18,7 @@ #include "content/public/browser/web_contents.h" #include "content/public/test/test_renderer_host.h" #include "mojo/public/cpp/bindings/remote.h" +#include "mojo/public/cpp/system/string_data_source.h" #include "net/base/isolation_info.h" #include "services/metrics/public/cpp/metrics_utils.h" #include "services/metrics/public/cpp/ukm_builders.h" @@ -897,7 +898,7 @@ EXPECT_EQ(prefetch_container->GetLastStreamingURLLoader(), nullptr); EXPECT_FALSE(prefetch_container->IsPrefetchServable(base::TimeDelta::Max())); - EXPECT_FALSE(prefetch_container->GetHead()); + EXPECT_TRUE(prefetch_container->GetHead()); EXPECT_TRUE(streaming_loaders[1]); base::RunLoop().RunUntilIdle(); @@ -1030,30 +1031,43 @@ auto pending_request = MakeManuallyServableStreamingURLLoaderForTest(prefetch_container.get()); + const auto producer_pipe_capacity = + network::features::GetDataPipeDefaultAllocationSize( + network::features::DataPipeAllocationSize::kLargerSizeIfPossible); + + const BodySize body_size = std::get<1>(GetParam()); std::string content; - switch (std::get<1>(GetParam())) { + switch (body_size) { case BodySize::kSmall: content = "Body"; break; case BodySize::kLarge: - while (content.size() < - 4 * GetDataPipeDefaultAllocationSize( - network::features::DataPipeAllocationSize:: - kLargerSizeIfPossible)) { - content += "Body"; - } + content = std::string(4 * producer_pipe_capacity, '-'); break; } + mojo::ScopedDataPipeConsumerHandle consumer_handle; + bool producer_completed = false; + { mojo::ScopedDataPipeProducerHandle producer_handle; - CHECK_EQ( - mojo::CreateDataPipe(content.size(), producer_handle, consumer_handle), - MOJO_RESULT_OK); - uint32_t bytes_written = content.size(); - CHECK_EQ(MOJO_RESULT_OK, - producer_handle->WriteData(content.data(), &bytes_written, - MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + ASSERT_EQ(mojo::CreateDataPipe(producer_pipe_capacity, producer_handle, + consumer_handle), + MOJO_RESULT_OK); + auto producer = + std::make_unique<mojo::DataPipeProducer>(std::move(producer_handle)); + mojo::DataPipeProducer* raw_producer = producer.get(); + raw_producer->Write(std::make_unique<mojo::StringDataSource>( + content, mojo::StringDataSource::AsyncWritingMode:: + STRING_STAYS_VALID_UNTIL_COMPLETION), + base::BindOnce( + [](std::unique_ptr<mojo::DataPipeProducer> producer, + bool* producer_completed, MojoResult result) { + *producer_completed = true; + DCHECK_EQ(result, MOJO_RESULT_OK); + // `producer` is deleted here. + }, + std::move(producer), &producer_completed)); } EXPECT_FALSE(prefetch_container->IsPrefetchServable(base::TimeDelta::Max())); @@ -1124,9 +1138,24 @@ break; // Completely read the body mojo pipe. - case Event::kCompleteBody: + case Event::kCompleteBody: { + if (body_size == BodySize::kLarge) { + // The body is sufficiently large to fill the data pipes and thus the + // producer should still have pending data to write before + // `StartDraining()`. + EXPECT_FALSE(producer_completed); + } + // Wait until the URLLoaderClient completion. + // `base::RunLoop().RunUntilIdle()` is not sufficient here, because + // `mojo::DataPipeProducer` uses thread pool. + base::RunLoop loop; + serving_url_loader_client->SetOnDataCompleteCallback( + loop.QuitClosure()); serving_url_loader_client->StartDraining(); + loop.Run(); + EXPECT_TRUE(producer_completed); break; + } case Event::kDestructPrefetchContainer: ASSERT_TRUE(prefetch_container);
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc index ded88943..7d3ce72 100644 --- a/content/browser/preloading/prefetch/prefetch_service.cc +++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -1332,19 +1332,15 @@ RecordPrefetchProxyPrefetchMainframeBodyLength(body_length); } - if (prefetch_container->GetLastStreamingURLLoader()) { + if (!prefetch_container->IsPrefetchServable(PrefetchCacheableDuration())) { // If the prefetch from the streaming URL loader cannot be served at this // point, then it can be discarded. - if (!prefetch_container->GetLastStreamingURLLoader()->Servable( - PrefetchCacheableDuration())) { - prefetch_container->ResetAllStreamingURLLoaders(); - } else { - PrefetchDocumentManager* prefetch_document_manager = - prefetch_container->GetPrefetchDocumentManager(); - if (prefetch_document_manager) { - prefetch_document_manager->OnPrefetchSuccessful( - prefetch_container.get()); - } + prefetch_container->ResetAllStreamingURLLoaders(); + } else { + PrefetchDocumentManager* prefetch_document_manager = + prefetch_container->GetPrefetchDocumentManager(); + if (prefetch_document_manager) { + prefetch_document_manager->OnPrefetchSuccessful(prefetch_container.get()); } }
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc index 1cb1875..cea2338 100644 --- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc +++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
@@ -68,7 +68,7 @@ } } -bool PrefetchStreamingURLLoader::Servable( +bool PrefetchResponseReader::Servable( base::TimeDelta cacheable_duration) const { // If the response hasn't been received yet (meaning response_complete_time_ // is absl::nullopt), we can still serve the prefetch (depending on |head_|). @@ -151,24 +151,22 @@ // Cached metadata is not supported for prefetch. cached_metadata.reset(); - head_ = std::move(head); - head_->was_in_prefetch_cache = true; + head->was_in_prefetch_cache = true; // Checks head to determine if the prefetch can be served. DCHECK(on_prefetch_response_started_callback_); - status_ = std::move(on_prefetch_response_started_callback_).Run(head_.get()); + status_ = std::move(on_prefetch_response_started_callback_).Run(head.get()); - // Update servable_ based on the returned status_ + bool servable = false; switch (status_) { case PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody: - servable_ = true; + servable = true; break; case PrefetchStreamingURLLoaderStatus::kPrefetchWasDecoy: case PrefetchStreamingURLLoaderStatus::kFailedInvalidHead: case PrefetchStreamingURLLoaderStatus::kFailedInvalidHeaders: case PrefetchStreamingURLLoaderStatus::kFailedNon2XX: case PrefetchStreamingURLLoaderStatus::kFailedMIMENotSupported: - servable_ = false; break; case PrefetchStreamingURLLoaderStatus::kWaitingOnHead: case PrefetchStreamingURLLoaderStatus::kRedirected_DEPRECATED: @@ -189,19 +187,21 @@ break; } - if (!servable_) { - if (on_received_head_callback_) { - std::move(on_received_head_callback_).Run(); - } - - return; + if (servable) { + head->navigation_delivery_type = + network::mojom::NavigationDeliveryType::kNavigationalPrefetch; + } else { + // Discard `body` for non-servable cases, to keep the existing behavior and + // also because `body` is not used. + body.reset(); } - head_->navigation_delivery_type = - network::mojom::NavigationDeliveryType::kNavigationalPrefetch; - + // `head` and `body` are discarded if `response_reader_` is `nullptr`, because + // it means the `PrefetchResponseReader` is deleted and thus we no longer + // serve the prefetched result. if (response_reader_) { - response_reader_->OnReceiveResponse(head_->Clone(), std::move(body)); + response_reader_->OnReceiveResponse(servable, std::move(head), + std::move(body)); } if (on_received_head_callback_) { @@ -256,7 +256,6 @@ } break; case PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect: - servable_ = false; if (on_received_head_callback_) { std::move(on_received_head_callback_).Run(); } @@ -303,34 +302,30 @@ DisconnectPrefetchURLLoaderMojo(); timeout_timer_.AbandonAndStop(); - completion_status_ = completion_status; - response_complete_time_ = base::TimeTicks::Now(); - if (status_ == PrefetchStreamingURLLoaderStatus::kWaitingOnHead || status_ == PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody) { - status_ = completion_status_->error_code == net::OK + status_ = completion_status.error_code == net::OK ? PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed : PrefetchStreamingURLLoaderStatus::kFailedNetError; } else if (status_ == PrefetchStreamingURLLoaderStatus:: kSuccessfulServedBeforeCompletion && - completion_status_->error_code != net::OK) { + completion_status.error_code != net::OK) { status_ = PrefetchStreamingURLLoaderStatus::kFailedNetErrorButServed; } - if (completion_status_->error_code != net::OK) { + if (response_reader_) { + response_reader_->OnComplete(completion_status); + } + + if (completion_status.error_code != net::OK) { // Note that we may have already started serving the prefetch if it was // marked as servable in |OnReceiveResponse|. - servable_ = false; if (on_received_head_callback_) { std::move(on_received_head_callback_).Run(); } } - std::move(on_prefetch_response_completed_callback_) - .Run(completion_status_.value()); - if (response_reader_) { - response_reader_->OnComplete(completion_status_.value()); - } + std::move(on_prefetch_response_completed_callback_).Run(completion_status); } void PrefetchStreamingURLLoader::OnStartServing() { @@ -341,12 +336,13 @@ kStopSwitchInNetworkContextForRedirect) { status_ = PrefetchStreamingURLLoaderStatus:: kServedSwitchInNetworkContextForRedirect; + } else if (response_reader_ && + response_reader_->GetCompletionStatus().has_value()) { + status_ = + PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion; } else { status_ = - completion_status_.has_value() - ? PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion - : PrefetchStreamingURLLoaderStatus:: - kSuccessfulServedBeforeCompletion; + PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion; } } @@ -449,15 +445,25 @@ void PrefetchResponseReader::OnComplete( network::URLLoaderCompletionStatus completion_status) { DCHECK(!last_event_added_); + DCHECK(!response_complete_time_); + DCHECK(!completion_status_); + last_event_added_ = true; + response_complete_time_ = base::TimeTicks::Now(); + completion_status_ = completion_status; + + if (completion_status.error_code != net::OK) { + servable_ = false; + } + if (serving_url_loader_client_ && event_queue_status_ == EventQueueStatus::kFinished) { - ForwardCompletionStatus(completion_status); + ForwardCompletionStatus(); return; } AddEventToQueue( base::BindOnce(&PrefetchResponseReader::ForwardCompletionStatus, - base::Unretained(this), completion_status)); + base::Unretained(this))); } void PrefetchResponseReader::OnReceiveEarlyHints( @@ -502,19 +508,25 @@ } void PrefetchResponseReader::OnReceiveResponse( + bool servable, network::mojom::URLResponseHeadPtr head, mojo::ScopedDataPipeConsumerHandle body) { DCHECK(!last_event_added_); DCHECK(event_queue_status_ == EventQueueStatus::kNotStarted); + DCHECK(!servable_); + DCHECK(!head_); + DCHECK(head); + + servable_ = servable; + head_ = std::move(head); AddEventToQueue(base::BindOnce(&PrefetchResponseReader::ForwardResponse, - base::Unretained(this), std::move(head), - std::move(body))); + base::Unretained(this), std::move(body))); } -void PrefetchResponseReader::ForwardCompletionStatus( - network::URLLoaderCompletionStatus completion_status) { +void PrefetchResponseReader::ForwardCompletionStatus() { DCHECK(serving_url_loader_client_); - serving_url_loader_client_->OnComplete(completion_status); + DCHECK(completion_status_); + serving_url_loader_client_->OnComplete(completion_status_.value()); } void PrefetchResponseReader::ForwardEarlyHints( @@ -537,13 +549,12 @@ } void PrefetchResponseReader::ForwardResponse( - network::mojom::URLResponseHeadPtr head, mojo::ScopedDataPipeConsumerHandle body) { DCHECK(serving_url_loader_client_); - DCHECK(head); + DCHECK(head_); DCHECK(body); - serving_url_loader_client_->OnReceiveResponse(std::move(head), - std::move(body), absl::nullopt); + serving_url_loader_client_->OnReceiveResponse(head_->Clone(), std::move(body), + absl::nullopt); } void PrefetchResponseReader::FollowRedirect(
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h index 173f7093..e8544cd8d 100644 --- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h +++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h
@@ -55,7 +55,8 @@ // via `AddEventToQueue()`) from the methods with the same names in // `PrefetchStreamingURLLoader`. void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints); - void OnReceiveResponse(network::mojom::URLResponseHeadPtr head, + void OnReceiveResponse(bool servable, + network::mojom::URLResponseHeadPtr head, mojo::ScopedDataPipeConsumerHandle body); void HandleRedirect(const net::RedirectInfo& redirect_info, network::mojom::URLResponseHeadPtr redirect_head); @@ -70,6 +71,13 @@ // Creates a request handler to serve the response of the prefetch. RequestHandler CreateRequestHandler(); + bool Servable(base::TimeDelta cacheable_duration) const; + absl::optional<network::URLLoaderCompletionStatus> GetCompletionStatus() + const { + return completion_status_; + } + const network::mojom::URLResponseHead* GetHead() const { return head_.get(); } + base::WeakPtr<PrefetchResponseReader> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } @@ -92,14 +100,12 @@ // Helper functions to send the appropriate events to // |serving_url_loader_client_|. - void ForwardCompletionStatus( - network::URLLoaderCompletionStatus completion_status); + void ForwardCompletionStatus(); void ForwardEarlyHints(network::mojom::EarlyHintsPtr early_hints); void ForwardTransferSizeUpdate(int32_t transfer_size_diff); void ForwardRedirect(const net::RedirectInfo& redirect_info, network::mojom::URLResponseHeadPtr); - void ForwardResponse(network::mojom::URLResponseHeadPtr head, - mojo::ScopedDataPipeConsumerHandle body); + void ForwardResponse(mojo::ScopedDataPipeConsumerHandle body); // network::mojom::URLLoader void FollowRedirect( @@ -130,6 +136,12 @@ // more events can be added. See the class comment for valid event sequences. bool last_event_added_ = false; + // The prefetched data and metadata. Not set for a redirect response. + network::mojom::URLResponseHeadPtr head_; + bool servable_{false}; + absl::optional<network::URLLoaderCompletionStatus> completion_status_; + absl::optional<base::TimeTicks> response_complete_time_; + // The URL loader client that will serve the prefetched data. mojo::Receiver<network::mojom::URLLoader> serving_url_loader_receiver_{this}; mojo::Remote<network::mojom::URLLoaderClient> serving_url_loader_client_; @@ -203,15 +215,8 @@ const net::RedirectInfo& redirect_info, network::mojom::URLResponseHeadPtr redirect_head); - bool Servable(base::TimeDelta cacheable_duration) const; bool Failed() const; - absl::optional<network::URLLoaderCompletionStatus> GetCompletionStatus() - const { - return completion_status_; - } - const network::mojom::URLResponseHead* GetHead() const { return head_.get(); } - void MakeSelfOwned(std::unique_ptr<PrefetchStreamingURLLoader> self); void PostTaskToDeleteSelf(); void PostTaskToDeleteSelfIfDisconnected(); @@ -275,12 +280,6 @@ // servable. base::OnceClosure on_received_head_callback_; - // The prefetched data and metadata. - network::mojom::URLResponseHeadPtr head_; - bool servable_{false}; - absl::optional<network::URLLoaderCompletionStatus> completion_status_; - absl::optional<base::TimeTicks> response_complete_time_; - base::WeakPtr<PrefetchResponseReader> response_reader_; base::WeakPtrFactory<PrefetchStreamingURLLoader> weak_ptr_factory_{this};
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc index c488c8d0..662658c 100644 --- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc +++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
@@ -239,13 +239,13 @@ on_head_received_loop.Run(); } - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_TRUE(response_reader->Servable(base::TimeDelta::Max())); test_url_loader_factory()->SimulateReceiveData(kBodyContent); test_url_loader_factory()->SimulateResponseComplete(net::OK); on_response_complete_loop.Run(); - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_TRUE(response_reader->Servable(base::TimeDelta::Max())); test_url_loader_factory()->DisconnectMojoPipes(); @@ -355,7 +355,7 @@ on_head_received_loop.Run(); } - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_TRUE(response_reader->Servable(base::TimeDelta::Max())); test_url_loader_factory()->SimulateReceiveData(kBodyContent1); @@ -479,7 +479,7 @@ on_head_received_loop.Run(); } - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_TRUE(response_reader->Servable(base::TimeDelta::Max())); test_url_loader_factory()->SimulateReceiveData(kBodyContent); test_url_loader_factory()->SimulateResponseComplete(net::OK); @@ -539,7 +539,7 @@ on_head_received_loop.Run(); } - EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max())); streaming_loader.reset(); @@ -599,13 +599,13 @@ on_head_received_loop.Run(); } - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_TRUE(response_reader->Servable(base::TimeDelta::Max())); test_url_loader_factory()->SimulateReceiveData(kBodyContent); test_url_loader_factory()->SimulateResponseComplete(net::ERR_FAILED); on_response_complete_loop.Run(); - EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max())); streaming_loader.reset(); @@ -659,7 +659,7 @@ on_head_received_loop.Run(); } - EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max())); streaming_loader.reset(); @@ -721,7 +721,7 @@ on_head_received_loop.Run(); } - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_TRUE(response_reader->Servable(base::TimeDelta::Max())); test_url_loader_factory()->SimulateReceiveData(kBodyContent); @@ -862,13 +862,13 @@ on_head_received_loop.Run(); } - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_TRUE(final_response_reader->Servable(base::TimeDelta::Max())); test_url_loader_factory()->SimulateReceiveData(kBodyContent); test_url_loader_factory()->SimulateResponseComplete(net::OK); on_response_complete_loop.Run(); - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_TRUE(final_response_reader->Servable(base::TimeDelta::Max())); // Simulates serving the redirect. base::WeakPtr<PrefetchResponseReader> weak_redirect_response_reader = @@ -1004,7 +1004,7 @@ on_head_received_loop.Run(); } - EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max())); streaming_loader.reset(); @@ -1070,7 +1070,7 @@ // The streaming_loader is marked as not servable, but it can serve the // redirect. The follow up streaming URL loader would then continue serving // the prefetch. - EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max())); base::WeakPtr<PrefetchResponseReader> weak_response_reader = response_reader->GetWeakPtr(); PrefetchResponseReader::RequestHandler redirect_handler = @@ -1171,7 +1171,7 @@ // Since the network URL loader was disconnected, then redirect cannot be // followed and the prefetch should not be servable. - EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max())); streaming_loader.reset(); @@ -1228,7 +1228,7 @@ kBodyContent.size()); on_response_received_loop.Run(); - EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max())); // On a decoy, the body pipe is closed since the data should not be stored. test_url_loader_factory()->SimulateReceiveData(kBodyContent, @@ -1288,7 +1288,7 @@ on_head_received_loop.Run(); } - EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max())); streaming_loader.reset(); @@ -1343,7 +1343,7 @@ kBodyContent.size()); on_response_received_loop.Run(); - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_TRUE(response_reader->Servable(base::TimeDelta::Max())); // Simulate serving the prefetch. This should stop the timeout timer. base::WeakPtr<PrefetchResponseReader> weak_response_reader = @@ -1374,7 +1374,8 @@ test_url_loader_factory()->SimulateResponseComplete(net::OK); on_response_complete_loop.Run(); - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + ASSERT_TRUE(weak_response_reader); + EXPECT_TRUE(weak_response_reader->Servable(base::TimeDelta::Max())); test_url_loader_factory()->DisconnectMojoPipes(); @@ -1454,7 +1455,7 @@ // The staleness of the streaming URL loader response is measured from when // the response is complete, not when the head is received. - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta())); + EXPECT_TRUE(response_reader->Servable(base::TimeDelta())); test_url_loader_factory()->SimulateReceiveData(kBodyContent); test_url_loader_factory()->SimulateResponseComplete(net::OK); @@ -1464,9 +1465,9 @@ // The response should not be servable if its been too long since it has // completed. - EXPECT_FALSE(streaming_loader->Servable(base::Seconds(3))); - EXPECT_FALSE(streaming_loader->Servable(base::Seconds(4))); - EXPECT_TRUE(streaming_loader->Servable(base::Seconds(5))); + EXPECT_FALSE(response_reader->Servable(base::Seconds(3))); + EXPECT_FALSE(response_reader->Servable(base::Seconds(4))); + EXPECT_TRUE(response_reader->Servable(base::Seconds(5))); streaming_loader.reset(); @@ -1523,7 +1524,7 @@ kBodyContent.size()); on_response_received_loop.Run(); - EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max())); + EXPECT_TRUE(response_reader->Servable(base::TimeDelta::Max())); // Simulates updating the transfer size. This event will be queued in the // streaming URL loader and sent to the serving URL loader once bound.
diff --git a/content/browser/preloading/prefetch/prefetch_test_utils.cc b/content/browser/preloading/prefetch/prefetch_test_utils.cc index d9938fd..248bcfd 100644 --- a/content/browser/preloading/prefetch/prefetch_test_utils.cc +++ b/content/browser/preloading/prefetch/prefetch_test_utils.cc
@@ -28,6 +28,8 @@ base::RunLoop on_response_received_loop; base::RunLoop on_response_complete_loop; + base::WeakPtr<PrefetchResponseReader> weak_response_reader = + prefetch_container->GetResponseReaderForCurrentPrefetch(); std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader = std::make_unique<PrefetchStreamingURLLoader>( &test_url_loader_factory, *request, TRAFFIC_ANNOTATION_FOR_TESTS, @@ -53,7 +55,7 @@ }), base::BindOnce(&PrefetchContainer::OnReceivedHead, prefetch_container->GetWeakPtr()), - prefetch_container->GetResponseReaderForCurrentPrefetch()); + weak_response_reader); auto weak_streaming_loader = streaming_loader->GetWeakPtr(); prefetch_container->TakeStreamingURLLoader(std::move(streaming_loader)); @@ -68,7 +70,8 @@ on_response_complete_loop.Run(); DCHECK(weak_streaming_loader); - DCHECK(weak_streaming_loader->Servable(base::TimeDelta::Max())); + DCHECK(weak_response_reader); + DCHECK(weak_response_reader->Servable(base::TimeDelta::Max())); } network::TestURLLoaderFactory::PendingRequest @@ -143,6 +146,8 @@ net::RedirectInfo redirect_info; network::mojom::URLResponseHeadPtr redirect_head; + auto weak_first_response_reader = + prefetch_container->GetResponseReaderForCurrentPrefetch(); std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader = std::make_unique<PrefetchStreamingURLLoader>( &test_url_loader_factory, *request, TRAFFIC_ANNOTATION_FOR_TESTS, @@ -165,7 +170,7 @@ &redirect_info, &redirect_head), base::BindOnce(&PrefetchContainer::OnReceivedHead, prefetch_container->GetWeakPtr()), - prefetch_container->GetResponseReaderForCurrentPrefetch()); + weak_first_response_reader); auto weak_streaming_loader = streaming_loader->GetWeakPtr(); prefetch_container->TakeStreamingURLLoader(std::move(streaming_loader)); @@ -194,14 +199,17 @@ // GetResponseReaderForCurrentPrefetch() now points to a new ResponseReader // after `AddRedirectHop()` above. DCHECK(weak_streaming_loader); - weak_streaming_loader->SetResponseReader( - prefetch_container->GetResponseReaderForCurrentPrefetch()); + auto weak_second_response_reader = + prefetch_container->GetResponseReaderForCurrentPrefetch(); + weak_streaming_loader->SetResponseReader(weak_second_response_reader); on_response_received_loop.Run(); on_response_complete_loop.Run(); DCHECK(weak_streaming_loader); - DCHECK(weak_streaming_loader->Servable(base::TimeDelta::Max())); + DCHECK(weak_first_response_reader); + DCHECK(weak_second_response_reader); + DCHECK(weak_second_response_reader->Servable(base::TimeDelta::Max())); } std::vector<base::WeakPtr<PrefetchStreamingURLLoader>> @@ -276,6 +284,8 @@ // Starts the followup PrefetchStreamingURLLoader. // GetResponseReaderForCurrentPrefetch() now points to a new ResponseReader // after `AddRedirectHop()` above. + base::WeakPtr<PrefetchResponseReader> weak_second_response_reader = + prefetch_container->GetResponseReaderForCurrentPrefetch(); auto second_streaming_loader = std::make_unique<PrefetchStreamingURLLoader>( &test_url_loader_factory, *redirect_request, TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(), @@ -298,7 +308,7 @@ }), base::BindOnce(&PrefetchContainer::OnReceivedHead, prefetch_container->GetWeakPtr()), - prefetch_container->GetResponseReaderForCurrentPrefetch()); + weak_second_response_reader); auto weak_second_streaming_loader = second_streaming_loader->GetWeakPtr(); prefetch_container->TakeStreamingURLLoader( @@ -314,7 +324,8 @@ on_response_complete_loop.Run(); DCHECK(weak_second_streaming_loader); - DCHECK(weak_second_streaming_loader->Servable(base::TimeDelta::Max())); + DCHECK(weak_second_response_reader); + DCHECK(weak_second_response_reader->Servable(base::TimeDelta::Max())); return {weak_first_streaming_loader, weak_second_streaming_loader}; } @@ -392,8 +403,16 @@ total_bytes_read_ += num_bytes; } +void PrefetchTestURLLoaderClient::SetOnDataCompleteCallback( + base::OnceClosure on_data_complete_callback) { + on_data_complete_callback_ = std::move(on_data_complete_callback); +} + void PrefetchTestURLLoaderClient::OnDataComplete() { body_finished_ = true; + if (on_data_complete_callback_) { + std::move(on_data_complete_callback_).Run(); + } } } // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_test_utils.h b/content/browser/preloading/prefetch/prefetch_test_utils.h index 86ab301..3aad036d 100644 --- a/content/browser/preloading/prefetch/prefetch_test_utils.h +++ b/content/browser/preloading/prefetch/prefetch_test_utils.h
@@ -87,6 +87,8 @@ return received_redirects_; } + void SetOnDataCompleteCallback(base::OnceClosure on_data_complete_callback); + private: // network::mojom::URLLoaderClient void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override; @@ -123,6 +125,8 @@ std::vector<std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>> received_redirects_; + + base::OnceClosure on_data_complete_callback_; }; } // namespace content
diff --git a/content/browser/renderer_host/frame_navigation_entry.cc b/content/browser/renderer_host/frame_navigation_entry.cc index 77372ee..1acf4881 100644 --- a/content/browser/renderer_host/frame_navigation_entry.cc +++ b/content/browser/renderer_host/frame_navigation_entry.cc
@@ -11,9 +11,6 @@ namespace content { -FrameNavigationEntry::FrameNavigationEntry() - : item_sequence_number_(-1), document_sequence_number_(-1), post_id_(-1) {} - FrameNavigationEntry::FrameNavigationEntry( const std::string& frame_unique_name, int64_t item_sequence_number, @@ -53,23 +50,22 @@ policy_container_policies_(std::move(policy_container_policies)), protect_url_in_navigation_api_(protect_url_in_navigation_api) {} -FrameNavigationEntry::~FrameNavigationEntry() {} +FrameNavigationEntry::~FrameNavigationEntry() = default; scoped_refptr<FrameNavigationEntry> FrameNavigationEntry::Clone() const { - auto copy = base::MakeRefCounted<FrameNavigationEntry>(); - // Omit any fields cleared at commit time. - copy->UpdateEntry( + auto copy = base::MakeRefCounted<FrameNavigationEntry>( frame_unique_name_, item_sequence_number_, document_sequence_number_, - navigation_api_key_, site_instance_.get(), nullptr, url_, - committed_origin_, referrer_, initiator_origin_, initiator_base_url_, - redirect_chain_, page_state_, method_, post_id_, - nullptr /* blob_url_loader_factory */, + navigation_api_key_, site_instance_, /*source_site_instance=*/nullptr, + url_, committed_origin_, referrer_, initiator_origin_, + initiator_base_url_, redirect_chain_, page_state_, method_, post_id_, + /*blob_url_loader_factory=*/nullptr, policy_container_policies_ ? policy_container_policies_->ClonePtr() : nullptr, protect_url_in_navigation_api_); - // |bindings_| gets only updated through the SetBindings API, not through - // UpdateEntry, so make a copy of it explicitly here as part of cloning. + + // |bindings_| gets only updated through the SetBindings API, so make a copy + // of it explicitly here as part of cloning. copy->bindings_ = bindings_; return copy; }
diff --git a/content/browser/renderer_host/frame_navigation_entry.h b/content/browser/renderer_host/frame_navigation_entry.h index 71b5ce2..fd83fe1 100644 --- a/content/browser/renderer_host/frame_navigation_entry.h +++ b/content/browser/renderer_host/frame_navigation_entry.h
@@ -41,7 +41,6 @@ // The value of bindings() before it is set during commit. enum : int { kInvalidBindings = -1 }; - FrameNavigationEntry(); FrameNavigationEntry( const std::string& frame_unique_name, int64_t item_sequence_number,
diff --git a/content/browser/renderer_host/input/touch_selection_controller_client_aura_browsertest.cc b/content/browser/renderer_host/input/touch_selection_controller_client_aura_browsertest.cc index 6ac4df5c..41561b5 100644 --- a/content/browser/renderer_host/input/touch_selection_controller_client_aura_browsertest.cc +++ b/content/browser/renderer_host/input/touch_selection_controller_client_aura_browsertest.cc
@@ -13,6 +13,7 @@ #include "base/run_loop.h" #include "base/task/single_thread_task_runner.h" #include "base/test/bind.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "base/test/test_timeouts.h" #include "build/chromeos_buildflags.h" @@ -45,6 +46,7 @@ #include "ui/events/test/event_generator.h" #include "ui/events/test/motion_event_test_utils.h" #include "ui/touch_selection/touch_selection_controller_test_api.h" +#include "ui/touch_selection/touch_selection_metrics.h" namespace content { namespace { @@ -1334,7 +1336,50 @@ ui::TouchSelectionController::INSERTION_ACTIVE); EXPECT_TRUE(ui::TouchSelectionMenuRunner::GetInstance()->IsRunning()); } -#endif +#endif // BUILDFLAG(IS_CHROMEOS) + +#if BUILDFLAG(IS_CHROMEOS_ASH) +// Tests that touch selection dragging records a histogram entry. +IN_PROC_BROWSER_TEST_P(TouchSelectionControllerClientAuraCAPFeatureTest, + SelectionDraggingMetrics) { + // Set the test page up. + ASSERT_NO_FATAL_FAILURE(StartTestWithPage("/touch_selection.html")); + + base::HistogramTester histogram_tester; + InitSelectionController(false); + RenderWidgetHostViewAura* rwhva = GetRenderWidgetHostViewAura(); + gfx::NativeView native_view = rwhva->GetNativeView(); + ui::test::EventGenerator generator(native_view->GetRootWindow()); + gfx::Point point_in_textfield = + gfx::ToRoundedPoint(GetPointInsideTextfield()); + generator.delegate()->ConvertPointFromTarget(native_view, + &point_in_textfield); + + // Long press drag selection. + SelectWithLongPress(generator, point_in_textfield); + InitiateTouchSelectionDragging(generator); + DragAndWaitForSelectionUpdate(generator, 9 * kCharacterWidth, 0); + generator.ReleaseTouch(); + histogram_tester.ExpectBucketCount(ui::kTouchSelectionDragTypeHistogramName, + ui::TouchSelectionDragType::kLongPressDrag, + 1); + histogram_tester.ExpectTotalCount(ui::kTouchSelectionDragTypeHistogramName, + 1); + + // Double press drag selection. Close the menu if needed so that it doesn't + // get in the way of the double press. + ui::TouchSelectionMenuRunner::GetInstance()->CloseMenu(); + SelectWithDoublePress(generator, point_in_textfield); + InitiateTouchSelectionDragging(generator); + DragAndWaitForSelectionUpdate(generator, 10 * kCharacterWidth, 0); + generator.ReleaseTouch(); + histogram_tester.ExpectBucketCount( + ui::kTouchSelectionDragTypeHistogramName, + ui::TouchSelectionDragType::kDoublePressDrag, 1); + histogram_tester.ExpectTotalCount(ui::kTouchSelectionDragTypeHistogramName, + 2); +} +#endif // BUILDFLAG(IS_CHROMEOS_ASH) // Tests that the quick menu is hidden whenever a touch point is active. // Flaky: https://crbug.com/803576
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index 4c923ed..809c248 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -12443,7 +12443,8 @@ if (!bypass_checks_for_error_page && !ValidateURLAndOrigin(params->url, params->origin, - is_same_document_navigation, navigation_request)) { + is_same_document_navigation, navigation_request, + params->origin_calculation_debug_info)) { return false; } @@ -12561,7 +12562,8 @@ const GURL& url, const url::Origin& origin, bool is_same_document_navigation, - NavigationRequest* navigation_request) { + NavigationRequest* navigation_request, + std::string origin_calculation_debug_info) { // file: URLs can be allowed to access any other origin, based on settings. if (origin.scheme() == url::kFileScheme) { auto prefs = GetOrCreateWebPreferences(); @@ -12615,7 +12617,8 @@ << " lock '" << process->GetProcessLock().ToString() << "'"; VLOG(1) << "Blocked URL " << url.spec(); LogCannotCommitUrlCrashKeys(url, is_same_document_navigation, - navigation_request); + navigation_request, + origin_calculation_debug_info); // Kills the process. bad_message::ReceivedBadMessage(process, @@ -12797,7 +12800,8 @@ // TODO(https://crbug.com/1131832): Make this a CHECK instead once we're // sure we never hit this case. LogCannotCommitUrlCrashKeys(params->url, is_same_document_navigation, - navigation_request.get()); + navigation_request.get(), + params->origin_calculation_debug_info); base::debug::DumpWithoutCrashing(); } @@ -12821,7 +12825,8 @@ if (!navigation_request && !is_synchronous_about_blank_commit && !is_same_document_navigation) { LogCannotCommitUrlCrashKeys(params->url, is_same_document_navigation, - navigation_request.get()); + navigation_request.get(), + params->origin_calculation_debug_info); bad_message::ReceivedBadMessage( GetProcess(), @@ -13974,7 +13979,8 @@ void RenderFrameHostImpl::LogCannotCommitUrlCrashKeys( const GURL& url, bool is_same_document_navigation, - NavigationRequest* navigation_request) { + NavigationRequest* navigation_request, + std::string& origin_calculation_debug_info) { LogRendererKillCrashKeys(GetSiteInstance()->GetSiteInfo()); // Temporary instrumentation to debug the root cause of renderer process @@ -14052,6 +14058,19 @@ last_successful_url_origin_key, last_successful_url().DeprecatedGetOriginAsURL().spec()); + static auto* const is_on_initial_empty_document_key = + base::debug::AllocateCrashKeyString("is_on_initial_empty_doc", + base::debug::CrashKeySize::Size32); + base::debug::SetCrashKeyString( + is_on_initial_empty_document_key, + bool_to_crash_key(frame_tree_node_->is_on_initial_empty_document())); + + static auto* const origin_calculation_debug_info_key = + base::debug::AllocateCrashKeyString("origin_calculation_debug_info", + base::debug::CrashKeySize::Size256); + base::debug::SetCrashKeyString(origin_calculation_debug_info_key, + origin_calculation_debug_info); + if (navigation_request && navigation_request->IsNavigationStarted()) { static auto* const is_renderer_initiated_key = base::debug::AllocateCrashKeyString("is_renderer_initiated",
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h index 46c539288..89572d0 100644 --- a/content/browser/renderer_host/render_frame_host_impl.h +++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -3665,10 +3665,12 @@ // Validates whether we can commit |url| and |origin| for a navigation or a // document.open() URL update. // A return value of true means that the URL & origin can be committed. - bool ValidateURLAndOrigin(const GURL& url, - const url::Origin& origin, - bool is_same_document_navigation, - NavigationRequest* navigation_request); + bool ValidateURLAndOrigin( + const GURL& url, + const url::Origin& origin, + bool is_same_document_navigation, + NavigationRequest* navigation_request, + std::string origin_calculation_debug_info = std::string()); // The actual implementation of committing a navigation in the browser // process. Called by the DidCommitProvisionalLoad IPC handler. @@ -3839,7 +3841,8 @@ // determines it cannot commit a URL or origin. void LogCannotCommitUrlCrashKeys(const GURL& url, bool is_same_document_navigation, - NavigationRequest* navigation_request); + NavigationRequest* navigation_request, + std::string& origin_calculation_debug_info); void LogCannotCommitOriginCrashKeys(const GURL& url, const url::Origin& origin, const ProcessLock& process_lock,
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index fb1bdc6..a036489 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -1432,8 +1432,12 @@ case 1: { RenderProcessHostImpl* host = static_cast<RenderProcessHostImpl*>( AllHostsIterator().GetCurrentValue()); - for (auto& observer : host->observers_) + for (auto& observer : host->observers_) { + observer.InProcessRendererExiting(host); + } + for (auto& observer : host->observers_) { observer.RenderProcessHostDestroyed(host); + } #ifndef NDEBUG host->is_self_deleted_ = true; #endif
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm index 98bbd0e..cc41925 100644 --- a/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm +++ b/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm
@@ -8,7 +8,7 @@ #include <stddef.h> #include <stdint.h> -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/memory/raw_ptr.h" #include "base/task/single_thread_task_runner.h" #include "base/test/task_environment.h"
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm index 8dea8bd..9bd1491 100644 --- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm +++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -10,10 +10,10 @@ #include <stdint.h> #include <tuple> +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/command_line.h" #include "base/containers/queue.h" #include "base/mac/scoped_cftyperef.h" -#include "base/mac/scoped_nsautorelease_pool.h" #include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" #include "base/run_loop.h" @@ -560,7 +560,7 @@ private: // This class isn't derived from PlatformTest. - base::mac::ScopedNSAutoreleasePool pool_; + base::apple::ScopedNSAutoreleasePool pool_; base::SimpleTestTickClock mock_clock_; };
diff --git a/content/browser/web_contents/web_drag_dest_mac_unittest.mm b/content/browser/web_contents/web_drag_dest_mac_unittest.mm index 2e18d64..0d4898b 100644 --- a/content/browser/web_contents/web_drag_dest_mac_unittest.mm +++ b/content/browser/web_contents/web_drag_dest_mac_unittest.mm
@@ -6,8 +6,8 @@ #include <AppKit/AppKit.h> +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/mac/mac_util.h" -#include "base/mac/scoped_nsautorelease_pool.h" #include "base/memory/ref_counted.h" #include "base/strings/sys_string_conversions.h" #include "content/public/common/drop_data.h" @@ -24,7 +24,7 @@ drag_dest_ = [[WebDragDest alloc] initWithWebContentsImpl:contents()]; } - base::mac::ScopedNSAutoreleasePool pool_; + base::apple::ScopedNSAutoreleasePool pool_; WebDragDest* __strong drag_dest_; };
diff --git a/content/child/child_thread_impl.cc b/content/child/child_thread_impl.cc index ae06033..94892349 100644 --- a/content/child/child_thread_impl.cc +++ b/content/child/child_thread_impl.cc
@@ -302,7 +302,7 @@ #if BUILDFLAG(IS_APPLE) void GetTaskPort(GetTaskPortCallback callback) override { mojo::PlatformHandle task_port( - (base::mac::ScopedMachSendRight(task_self_trap()))); + (base::apple::ScopedMachSendRight(task_self_trap()))); std::move(callback).Run(std::move(task_port)); } #endif
diff --git a/content/public/app/content_main.h b/content/public/app/content_main.h index 9be99b2..876ee727 100644 --- a/content/public/app/content_main.h +++ b/content/public/app/content_main.h
@@ -16,11 +16,11 @@ #include <windows.h> #endif -namespace base { -namespace mac { +#if BUILDFLAG(IS_MAC) +namespace base::apple { class ScopedNSAutoreleasePool; } -} +#endif namespace sandbox { struct SandboxInterfaceInfo; @@ -71,7 +71,7 @@ #if BUILDFLAG(IS_MAC) // The outermost autorelease pool to pass to main entry points. - raw_ptr<base::mac::ScopedNSAutoreleasePool> autorelease_pool = nullptr; + raw_ptr<base::apple::ScopedNSAutoreleasePool> autorelease_pool = nullptr; #endif // Returns a copy of this ContentMainParams without the move-only data
diff --git a/content/public/browser/render_process_host_observer.h b/content/public/browser/render_process_host_observer.h index e5b06e1..208bceb9 100644 --- a/content/public/browser/render_process_host_observer.h +++ b/content/public/browser/render_process_host_observer.h
@@ -44,6 +44,13 @@ virtual void RenderProcessExited(RenderProcessHost* host, const ChildProcessTerminationInfo& info) {} + // This is the equivalent to the `RenderProcessExited` notification above but + // for --single-process mode only. This is invoked just before calling + // `RenderProcessHostDestroyed`. Useful for observers that needs the two-step + // destruction mechanism of RenderProcessHost objects, even in + // --single--process mode, allowing the logic to be shared between both modes. + virtual void InProcessRendererExiting(RenderProcessHost* host) {} + // This method is invoked when the observed RenderProcessHost itself is // destroyed. This is guaranteed to be the last call made to the observer, so // if the observer is tied to the observed RenderProcessHost, it is safe to
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn index c86085f..4be59cfc 100644 --- a/content/public/common/BUILD.gn +++ b/content/public/common/BUILD.gn
@@ -171,6 +171,7 @@ "content_paths.h", "content_switch_dependent_feature_overrides.cc", "content_switch_dependent_feature_overrides.h", + "dips_utils.h", "drop_data.cc", "drop_data.h", "input_event_ack_state.h",
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc index 7fe16d9..d67b69d 100644 --- a/content/public/common/content_features.cc +++ b/content/public/common/content_features.cc
@@ -11,6 +11,7 @@ #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "content/common/buildflags.h" +#include "content/public/common/dips_utils.h" namespace features { @@ -298,6 +299,65 @@ #endif ); +// Enables the DIPS (Detect Incidental Party State) feature. +// On by default to allow for collecting metrics. All potentially dangerous +// behavior (database persistence, DIPS deletion) will be gated by params. +BASE_FEATURE(kDIPS, "DIPS", base::FEATURE_ENABLED_BY_DEFAULT); + +// Set whether DIPS persists its database to disk. +const base::FeatureParam<bool> kDIPSPersistedDatabaseEnabled{ + &kDIPS, "persist_database", true}; + +// Set whether DIPS performs deletion. +const base::FeatureParam<bool> kDIPSDeletionEnabled{&kDIPS, "delete", false}; + +// Set the time period that Chrome will wait for before clearing storage for a +// site after it performs some action (e.g. bouncing the user or using storage) +// without user interaction. +const base::FeatureParam<base::TimeDelta> kDIPSGracePeriod{ + &kDIPS, "grace_period", base::Hours(1)}; + +// Set the cadence at which Chrome will attempt to clear incidental state +// repeatedly. +const base::FeatureParam<base::TimeDelta> kDIPSTimerDelay{&kDIPS, "timer_delay", + base::Hours(1)}; + +// Sets how long DIPS maintains interactions and Web Authn Assertions (WAA) for +// a site. +// +// If a site in the DIPS database has an interaction or WAA within the grace +// period a DIPS-triggering action, then that action and all ensuing actions are +// protected from DIPS clearing until the interaction and WAA "expire" as set +// by this param. +// NOTE: Updating this param name (to reflect WAA) is deemed unnecessary as far +// as readability is concerned. +const base::FeatureParam<base::TimeDelta> kDIPSInteractionTtl{ + &kDIPS, "interaction_ttl", base::Days(45)}; + +constexpr base::FeatureParam<content::DIPSTriggeringAction>::Option + kDIPSTriggeringActionOptions[] = { + {content::DIPSTriggeringAction::kNone, "none"}, + {content::DIPSTriggeringAction::kStorage, "storage"}, + {content::DIPSTriggeringAction::kBounce, "bounce"}, + {content::DIPSTriggeringAction::kStatefulBounce, "stateful_bounce"}}; + +// Sets the actions which will trigger DIPS clearing for a site. The default is +// to set to kBounce, but can be overridden by Finch experiment groups, +// command-line flags, or chrome flags. +// +// Note: Maintain a matching nomenclature of the options with the feature flag +// entries at about_flags.cc. +const base::FeatureParam<content::DIPSTriggeringAction> kDIPSTriggeringAction{ + &kDIPS, "triggering_action", content::DIPSTriggeringAction::kNone, + &kDIPSTriggeringActionOptions}; + +// Denotes the length of a time interval within which any client-side redirect +// is viewed as a bounce (provided all other criteria are equally met). The +// interval starts every time a page finishes a navigation (a.k.a. a commit is +// registered). +const base::FeatureParam<base::TimeDelta> kDIPSClientBounceDetectionTimeout{ + &kDIPS, "client_bounce_detection_timeout", base::Seconds(10)}; + // Enable document policy for configuring and restricting feature behavior. BASE_FEATURE(kDocumentPolicy, "DocumentPolicy",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h index e71c0d8..88ab546 100644 --- a/content/public/common/content_features.h +++ b/content/public/common/content_features.h
@@ -14,6 +14,7 @@ #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "content/common/content_export.h" +#include "content/public/common/dips_utils.h" namespace features { @@ -64,6 +65,19 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE(kDesktopPWAsTabStrip); CONTENT_EXPORT BASE_DECLARE_FEATURE(kDevicePosture); CONTENT_EXPORT BASE_DECLARE_FEATURE(kDigitalGoodsApi); +CONTENT_EXPORT BASE_DECLARE_FEATURE(kDIPS); +CONTENT_EXPORT extern const base::FeatureParam<bool> + kDIPSPersistedDatabaseEnabled; +CONTENT_EXPORT extern const base::FeatureParam<bool> kDIPSDeletionEnabled; +CONTENT_EXPORT extern const base::FeatureParam<base::TimeDelta> + kDIPSGracePeriod; +CONTENT_EXPORT extern const base::FeatureParam<base::TimeDelta> kDIPSTimerDelay; +CONTENT_EXPORT extern const base::FeatureParam<base::TimeDelta> + kDIPSInteractionTtl; +CONTENT_EXPORT extern const base::FeatureParam<content::DIPSTriggeringAction> + kDIPSTriggeringAction; +CONTENT_EXPORT extern const base::FeatureParam<base::TimeDelta> + kDIPSClientBounceDetectionTimeout; CONTENT_EXPORT BASE_DECLARE_FEATURE(kDocumentPolicy); CONTENT_EXPORT BASE_DECLARE_FEATURE(kDocumentPolicyNegotiation); CONTENT_EXPORT BASE_DECLARE_FEATURE(kEarlyEstablishGpuChannel);
diff --git a/content/public/common/dips_utils.h b/content/public/common/dips_utils.h new file mode 100644 index 0000000..403a928 --- /dev/null +++ b/content/public/common/dips_utils.h
@@ -0,0 +1,14 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_PUBLIC_COMMON_DIPS_UTILS_H_ +#define CONTENT_PUBLIC_COMMON_DIPS_UTILS_H_ + +namespace content { + +enum class DIPSTriggeringAction { kNone, kStorage, kBounce, kStatefulBounce }; + +} // namespace content + +#endif // CONTENT_PUBLIC_COMMON_DIPS_UTILS_H_
diff --git a/content/public/common/main_function_params.h b/content/public/common/main_function_params.h index bc235aa..6795430 100644 --- a/content/public/common/main_function_params.h +++ b/content/public/common/main_function_params.h
@@ -19,11 +19,9 @@ struct SandboxInterfaceInfo; } #elif BUILDFLAG(IS_MAC) -namespace base { -namespace mac { +namespace base::apple { class ScopedNSAutoreleasePool; } -} #endif namespace content { @@ -52,7 +50,7 @@ #elif BUILDFLAG(IS_MAC) // This field is not a raw_ptr<> because it was filtered by the rewriter // for: #union - RAW_PTR_EXCLUSION base::mac::ScopedNSAutoreleasePool* autorelease_pool = + RAW_PTR_EXCLUSION base::apple::ScopedNSAutoreleasePool* autorelease_pool = nullptr; #elif BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) bool zygote_child = false;
diff --git a/content/public/test/content_browser_test.cc b/content/public/test/content_browser_test.cc index ef5c8ce..057bf2e 100644 --- a/content/public/test/content_browser_test.cc +++ b/content/public/test/content_browser_test.cc
@@ -142,7 +142,7 @@ // deallocation via an autorelease pool (such as browser window closure and // browser shutdown). To avoid this, the following pool is recycled after each // time code is directly executed. - pool_ = new base::mac::ScopedNSAutoreleasePool; + pool_ = new base::apple::ScopedNSAutoreleasePool; #endif // Pump startup related events.
diff --git a/content/public/test/content_browser_test.h b/content/public/test/content_browser_test.h index 50b0115..4445da94d 100644 --- a/content/public/test/content_browser_test.h +++ b/content/public/test/content_browser_test.h
@@ -34,7 +34,7 @@ #include "content/public/test/browser_test_base.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/test/scoped_path_override.h" #include "third_party/abseil-cpp/absl/types/optional.h" #endif @@ -80,7 +80,7 @@ // deallocation via an autorelease pool (such as browser window closure and // browser shutdown). To avoid this, the following pool is recycled after each // time code is directly executed. - raw_ptr<base::mac::ScopedNSAutoreleasePool> pool_ = nullptr; + raw_ptr<base::apple::ScopedNSAutoreleasePool> pool_ = nullptr; absl::optional<base::ScopedPathOverride> file_exe_override_; #endif
diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc index e1d94801..a7d2707f 100644 --- a/content/public/test/render_view_test.cc +++ b/content/public/test/render_view_test.cc
@@ -75,7 +75,7 @@ #include "v8/include/v8.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif #if BUILDFLAG(IS_WIN) @@ -387,7 +387,7 @@ #endif #if BUILDFLAG(IS_MAC) - autorelease_pool_ = std::make_unique<base::mac::ScopedNSAutoreleasePool>(); + autorelease_pool_ = std::make_unique<base::apple::ScopedNSAutoreleasePool>(); #endif command_line_ = std::make_unique<base::CommandLine>(base::CommandLine::NO_PROGRAM);
diff --git a/content/public/test/render_view_test.h b/content/public/test/render_view_test.h index a652453..5e934d3c 100644 --- a/content/public/test/render_view_test.h +++ b/content/public/test/render_view_test.h
@@ -243,7 +243,7 @@ mojo::BinderMap binders_; #if BUILDFLAG(IS_MAC) - std::unique_ptr<base::mac::ScopedNSAutoreleasePool> autorelease_pool_; + std::unique_ptr<base::apple::ScopedNSAutoreleasePool> autorelease_pool_; #endif private:
diff --git a/content/public/test/test_launcher.cc b/content/public/test/test_launcher.cc index b5dfa67..0ba08a1 100644 --- a/content/public/test/test_launcher.cc +++ b/content/public/test/test_launcher.cc
@@ -68,7 +68,7 @@ // To avoid conflicts with the macro from the Windows SDK... #undef GetCommandLine #elif BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "sandbox/mac/seatbelt_exec.h" #endif
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index 4e44ec9..3f3217d 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc
@@ -5346,6 +5346,16 @@ !info->is_fullscreen_requested; if (should_do_synchronous_about_blank_navigation) { + if (!IsMainFrame()) { + // Synchronous about:blank commits on iframes should only be triggered + // when first creating the iframe with an unset/about:blank URL, which + // means the origin should inherit from the parent. + WebLocalFrame* parent = static_cast<WebLocalFrame*>(frame_->Parent()); + CHECK(parent); + CHECK(parent->GetDocument().GetSecurityOrigin().IsSameOriginWith( + info->url_request.RequestorOrigin())); + } + for (auto& observer : observers_) observer.DidStartNavigation(url, info->navigation_type); SynchronouslyCommitAboutBlankForBug778318(std::move(info));
diff --git a/content/renderer/renderer_main.cc b/content/renderer/renderer_main.cc index e58f65f..855f2d8 100644 --- a/content/renderer/renderer_main.cc +++ b/content/renderer/renderer_main.cc
@@ -60,7 +60,7 @@ #include <signal.h> #include <unistd.h> -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/message_loop/message_pump_apple.h" #include "third_party/blink/public/web/web_view.h" #endif // BUILDFLAG(IS_MAC) @@ -151,7 +151,7 @@ const base::CommandLine& command_line = *parameters.command_line; #if BUILDFLAG(IS_MAC) - base::mac::ScopedNSAutoreleasePool* pool = parameters.autorelease_pool; + base::apple::ScopedNSAutoreleasePool* pool = parameters.autorelease_pool; #endif // BUILDFLAG(IS_MAC) #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/content/test/content_test_suite.cc b/content/test/content_test_suite.cc index 70b1117e..90a51ea 100644 --- a/content/test/content_test_suite.cc +++ b/content/test/content_test_suite.cc
@@ -23,7 +23,7 @@ #endif #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/test/mock_chrome_application_mac.h" #endif @@ -37,7 +37,7 @@ void ContentTestSuite::Initialize() { #if BUILDFLAG(IS_MAC) - base::mac::ScopedNSAutoreleasePool autorelease_pool; + base::apple::ScopedNSAutoreleasePool autorelease_pool; mock_cr_app::RegisterMockCrApp(); #endif
diff --git a/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt index 56e2b70..5e3f2cdf 100644 --- a/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt +++ b/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt
@@ -129,9 +129,6 @@ crbug.com/1424154 [ win amd-0x7340 ] WebCodecs_PerFrameQpEncoding_offscreen_avc1.42001E_prefer-hardware [ Failure ] -crbug.com/1469466 [ android android-nexus-5x ] WebCodecs_EncodeColorSpace_avc1.42001E_prefer-hardware [ Failure ] -crbug.com/1469466 [ android android-nexus-5x ] WebCodecs_EncodeColorSpace_vp8_prefer-hardware [ Failure ] - crbug.com/1470336 [ android-oreo android-nexus-5x ] WebCodecs_EncodingRateControl_vp8_prefer-hardware_constant_3000000 [ Failure ] #######################################################################
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt index 6f4b0de..72ae78c 100644 --- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt +++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -140,7 +140,7 @@ # Extension only available through passthrough decoder? crbug.com/849576 [ no-passthrough ] WebglExtension_KHR_parallel_shader_compile [ Skip ] # KHR_parallel_shader_compile will not be exposed on OpenGL drivers that don't have support for it. -crbug.com/angleproject/3031 [ android angle-opengl passthrough ] WebglExtension_KHR_parallel_shader_compile [ Skip ] +crbug.com/angleproject/3031 [ android angle-opengles passthrough ] WebglExtension_KHR_parallel_shader_compile [ Skip ] crbug.com/angleproject/3031 [ mac angle-opengl passthrough ] WebglExtension_KHR_parallel_shader_compile [ Skip ] crbug.com/angleproject/3031 [ win angle-opengl passthrough ] WebglExtension_KHR_parallel_shader_compile [ Skip ]
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt index 70cee0b..7c321d6 100644 --- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt +++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -129,7 +129,7 @@ crbug.com/808744 [ fuchsia ] WebglExtension_EXT_disjoint_timer_query [ Skip ] crbug.com/849576 [ no-passthrough ] WebglExtension_KHR_parallel_shader_compile [ Skip ] # KHR_parallel_shader_compile will not be exposed on OpenGL drivers that don't have support for it. -crbug.com/angleproject/3031 [ android angle-opengl passthrough ] WebglExtension_KHR_parallel_shader_compile [ Skip ] +crbug.com/angleproject/3031 [ android angle-opengles passthrough ] WebglExtension_KHR_parallel_shader_compile [ Skip ] crbug.com/angleproject/3031 [ mac angle-opengl passthrough ] WebglExtension_KHR_parallel_shader_compile [ Skip ] crbug.com/angleproject/3031 [ win angle-opengl passthrough ] WebglExtension_KHR_parallel_shader_compile [ Skip ] crbug.com/776222 [ android ] WebglExtension_WEBGL_video_texture [ Skip ]
diff --git a/content/test/test_blink_web_unit_test_support.cc b/content/test/test_blink_web_unit_test_support.cc index 1281acf..bb82132b 100644 --- a/content/test/test_blink_web_unit_test_support.cc +++ b/content/test/test_blink_web_unit_test_support.cc
@@ -43,8 +43,8 @@ #include "v8/include/v8.h" #if BUILDFLAG(IS_APPLE) +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/mac/foundation_util.h" -#include "base/mac/scoped_nsautorelease_pool.h" #endif #ifdef V8_USE_EXTERNAL_STARTUP_DATA @@ -76,7 +76,7 @@ TestBlinkWebUnitTestSupport::TestBlinkWebUnitTestSupport( TestBlinkWebUnitTestSupport::SchedulerType scheduler_type) { #if BUILDFLAG(IS_APPLE) - base::mac::ScopedNSAutoreleasePool autorelease_pool; + base::apple::ScopedNSAutoreleasePool autorelease_pool; #endif #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
diff --git a/content/utility/services.cc b/content/utility/services.cc index 2eec7298..64501098 100644 --- a/content/utility/services.cc +++ b/content/utility/services.cc
@@ -35,7 +35,7 @@ #include "services/video_capture/video_capture_service_impl.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/mach_logging.h" +#include "base/apple/mach_logging.h" #include "sandbox/mac/system_services.h" #include "sandbox/policy/sandbox.h" #endif
diff --git a/device/fido/fido_request_handler_base.cc b/device/fido/fido_request_handler_base.cc index 61c97534..dea59c1 100644 --- a/device/fido/fido_request_handler_base.cc +++ b/device/fido/fido_request_handler_base.cc
@@ -196,9 +196,6 @@ FIDO_LOG(ERROR) << "Cannot test Bluetooth power status because process is " "not self-responsible. Launch from Finder to fix."; } - - transport_availability_info_.has_icloud_drive_enabled = - fido::icloud_keychain::IsICloudDriveEnabled(); #else const bool can_call_ble_apis = true; #endif
diff --git a/device/fido/fido_request_handler_base.h b/device/fido/fido_request_handler_base.h index 0fb006b391..0ae1a50 100644 --- a/device/fido/fido_request_handler_base.h +++ b/device/fido/fido_request_handler_base.h
@@ -178,12 +178,6 @@ // this request. ConditionalUITreatment conditional_ui_treatment = ConditionalUITreatment::kDefault; - - // has_icloud_drive_enabled returns true if we believe that the user is - // current syncing with iCloud Drive. This is used as an approximation to - // "has iCloud Keychain" enabled, which is what we would like to know but - // cannot easily learn. - bool has_icloud_drive_enabled = false; }; class COMPONENT_EXPORT(DEVICE_FIDO) Observer {
diff --git a/device/fido/mac/icloud_keychain.h b/device/fido/mac/icloud_keychain.h index e7d39be..d3c9217a 100644 --- a/device/fido/mac/icloud_keychain.h +++ b/device/fido/mac/icloud_keychain.h
@@ -18,9 +18,6 @@ // the lifetime of the process. COMPONENT_EXPORT(DEVICE_FIDO) bool IsSupported(); -// IsICloudDriveSyncing returns true if iCloud Drive is available. -COMPONENT_EXPORT(DEVICE_FIDO) bool IsICloudDriveEnabled(); - // NewDiscovery returns a discovery that will immediately find an iCloud // Keychain authenticator. It is only valid to call this if `IsSupported` // returned true. It takes an `NSWindow*` to indicate the window which the
diff --git a/device/fido/mac/icloud_keychain.mm b/device/fido/mac/icloud_keychain.mm index c7d600cf..c076ad5 100644 --- a/device/fido/mac/icloud_keychain.mm +++ b/device/fido/mac/icloud_keychain.mm
@@ -369,10 +369,6 @@ return false; } -bool IsICloudDriveEnabled() { - return [NSFileManager defaultManager].ubiquityIdentityToken != nil; -} - std::unique_ptr<FidoDiscoveryBase> NewDiscovery(uintptr_t ns_window) { if (@available(macOS 13.3, *)) { NSWindow* window = (__bridge NSWindow*)(void*)ns_window;
diff --git a/docs/testing/chromeos_integration/development_guide.md b/docs/testing/chromeos_integration/development_guide.md index 8e5c490e..e08dbde 100644 --- a/docs/testing/chromeos_integration/development_guide.md +++ b/docs/testing/chromeos_integration/development_guide.md
@@ -15,7 +15,7 @@ run on DUT. ## How to run Ash test -Please see: [go/crosier-run](go/crosier-run) +Please see: [go/crosier-run](http://go/crosier-run) ## How to run Lacros test See the [demo test](https://source.chromium.org/chromium/chromium/src/+/main:chrome/test/base/chromeos/crosier/demo_integration_test.cc;l=19)
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn index c9a1cb6b..f110f3dc 100644 --- a/extensions/browser/BUILD.gn +++ b/extensions/browser/BUILD.gn
@@ -484,7 +484,6 @@ "mojo/keep_alive_impl.h", "network_permissions_updater.cc", "network_permissions_updater.h", - "notification_types.h", "null_app_sorting.cc", "null_app_sorting.h", "offscreen_document_host.cc",
diff --git a/extensions/browser/api/system_cpu/cpu_info_provider_mac.cc b/extensions/browser/api/system_cpu/cpu_info_provider_mac.cc index 1808564..d6834d46 100644 --- a/extensions/browser/api/system_cpu/cpu_info_provider_mac.cc +++ b/extensions/browser/api/system_cpu/cpu_info_provider_mac.cc
@@ -6,8 +6,8 @@ #include <mach/mach_host.h> +#include "base/apple/scoped_mach_port.h" #include "base/mac/mac_util.h" -#include "base/mac/scoped_mach_port.h" #include "base/system/sys_info.h" namespace extensions { @@ -27,7 +27,7 @@ DCHECK(infos); natural_t num_of_processors; - base::mac::ScopedMachSendRight host(mach_host_self()); + base::apple::ScopedMachSendRight host(mach_host_self()); mach_msg_type_number_t type; processor_cpu_load_info_data_t* cpu_infos;
diff --git a/extensions/browser/api/system_info/system_info_api_unittest.cc b/extensions/browser/api/system_info/system_info_api_unittest.cc index 85c3a4d..6d66ec8 100644 --- a/extensions/browser/api/system_info/system_info_api_unittest.cc +++ b/extensions/browser/api/system_info/system_info_api_unittest.cc
@@ -52,7 +52,7 @@ }; // TestExtensionsBrowserClient: - bool IsValidContext(content::BrowserContext* context) override { + bool IsValidContext(void* context) override { return TestExtensionsBrowserClient::IsValidContext(context) || context == second_context_; }
diff --git a/extensions/browser/app_window/app_window.cc b/extensions/browser/app_window/app_window.cc index 8c33490..318be839 100644 --- a/extensions/browser/app_window/app_window.cc +++ b/extensions/browser/app_window/app_window.cc
@@ -41,7 +41,6 @@ #include "extensions/browser/extension_system.h" #include "extensions/browser/extension_web_contents_observer.h" #include "extensions/browser/extensions_browser_client.h" -#include "extensions/browser/notification_types.h" #include "extensions/browser/process_manager.h" #include "extensions/browser/suggest_permission_util.h" #include "extensions/browser/view_type_utils.h"
diff --git a/extensions/browser/events/event_ack_data.cc b/extensions/browser/events/event_ack_data.cc index de1e1cf..9a4437c40 100644 --- a/extensions/browser/events/event_ack_data.cc +++ b/extensions/browser/events/event_ack_data.cc
@@ -9,6 +9,7 @@ #include "base/functional/bind.h" #include "base/functional/callback.h" +#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/uuid.h" #include "content/public/browser/browser_task_traits.h" @@ -17,6 +18,8 @@ #include "content/public/browser/service_worker_external_request_result.h" #include "extensions/browser/event_router.h" +constexpr base::TimeDelta kEventAckMetricTimeLimit = base::Minutes(5); + namespace extensions { EventAckData::EventAckData() = default; @@ -51,6 +54,22 @@ event_id, EventInfo{request_uuid, render_process_id, start_ok, dispatch_start_time, dispatch_source}); DCHECK(insert_result.second) << "EventAckData: Duplicate event_id."; + + base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&EventAckData::EmitLateAckedEventTask, + weak_factory_.GetWeakPtr(), event_id), + kEventAckMetricTimeLimit); +} + +void EventAckData::EmitLateAckedEventTask(int event_id) { + // If the event is still present then we haven't received the ack yet in + // `EventAckData::DecrementInflightEvent()`. + if (unacked_events_.contains(event_id)) { + base::UmaHistogramBoolean( + "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker", + false); + } } void EventAckData::DecrementInflightEvent( @@ -89,6 +108,17 @@ /*bucket_count=*/100); } + // Emit only if we're within the expected event ack time limit. We'll take + // care of the emit for a late ack via a delayed task. + bool late_ack = + (base::TimeTicks::Now() - request_info_iter->second.dispatch_start_time) > + kEventAckMetricTimeLimit; + if (!late_ack) { + base::UmaHistogramBoolean( + "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker", + true); + } + base::Uuid request_uuid = std::move(event_info.request_uuid); bool start_ok = event_info.start_ok; unacked_events_.erase(request_info_iter);
diff --git a/extensions/browser/events/event_ack_data.h b/extensions/browser/events/event_ack_data.h index b795df70..051966e17 100644 --- a/extensions/browser/events/event_ack_data.h +++ b/extensions/browser/events/event_ack_data.h
@@ -78,6 +78,11 @@ EventDispatchSource dispatch_source; }; + // Emits a stale event ack metric if an event with `event_id` is not present + // in `unacked_events_`. Meaning that the event was not yet acked by the + // renderer to the browser. + void EmitLateAckedEventTask(int event_id); + // TODO(crbug.com/1441221): Mark events that are not acked within 5 minutes // (if the worker is still around) as stale, and emit // Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2 at that point.
diff --git a/extensions/browser/extensions_browser_client.h b/extensions/browser/extensions_browser_client.h index c8b79578..058367b 100644 --- a/extensions/browser/extensions_browser_client.h +++ b/extensions/browser/extensions_browser_client.h
@@ -129,8 +129,10 @@ virtual bool AreExtensionsDisabled(const base::CommandLine& command_line, content::BrowserContext* context) = 0; - // Returns true if the |context| is known to the embedder. - virtual bool IsValidContext(content::BrowserContext* context) = 0; + // Returns true if the `context` is known to the embedder. + // Note: This is a `void*` to ensure downstream uses do not use the `context` + // in case it is *not* valid. + virtual bool IsValidContext(void* context) = 0; // Returns true if the BrowserContexts could be considered equivalent, for // example, if one is an off-the-record context owned by the other.
diff --git a/extensions/browser/notification_types.h b/extensions/browser/notification_types.h deleted file mode 100644 index 73d8cec..0000000 --- a/extensions/browser/notification_types.h +++ /dev/null
@@ -1,56 +0,0 @@ -// Copyright 2014 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef EXTENSIONS_BROWSER_NOTIFICATION_TYPES_H_ -#define EXTENSIONS_BROWSER_NOTIFICATION_TYPES_H_ - -#include "content/public/browser/notification_types.h" -#include "extensions/buildflags/buildflags.h" - -#if !BUILDFLAG(ENABLE_EXTENSIONS) -#error "Extensions must be enabled" -#endif - -// ** -// ** NOTICE -// ** -// ** The notification system is deprecated, obsolete, and is slowly being -// ** removed. See https://crbug.com/268984 and https://crbug.com/411569. -// ** -// ** Please don't add any new notification types, and please help migrate -// ** existing uses of the notification types below to use the Observer and -// ** Callback patterns. -// ** - -namespace extensions { - -// Only notifications fired by the extensions module should be here. The -// extensions module should not listen to notifications fired by the -// embedder. -enum NotificationType { - // WARNING: This need to match chrome/browser/chrome_notification_types.h. - NOTIFICATION_EXTENSIONS_START = content::NOTIFICATION_CONTENT_END, - - // An error occurred during extension install. The details are a string with - // details about why the install failed. - // TODO(https://crbug.com/1174734): Remove. - NOTIFICATION_EXTENSION_INSTALL_ERROR, - - NOTIFICATION_EXTENSIONS_END -}; - -// ** -// ** NOTICE -// ** -// ** The notification system is deprecated, obsolete, and is slowly being -// ** removed. See https://crbug.com/268984 and https://crbug.com/411569. -// ** -// ** Please don't add any new notification types, and please help migrate -// ** existing uses of the notification types below to use the Observer and -// ** Callback patterns. -// ** - -} // namespace extensions - -#endif // EXTENSIONS_BROWSER_NOTIFICATION_TYPES_H_
diff --git a/extensions/browser/process_manager.cc b/extensions/browser/process_manager.cc index 0477e2a..b031e74 100644 --- a/extensions/browser/process_manager.cc +++ b/extensions/browser/process_manager.cc
@@ -13,6 +13,7 @@ #include "base/location.h" #include "base/logging.h" #include "base/metrics/field_trial_params.h" +#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/observer_list.h" #include "base/one_shot_event.h" @@ -765,15 +766,23 @@ base::Uuid request_uuid = base::Uuid::GenerateRandomV4(); - service_worker_keepalives_[request_uuid] = ServiceWorkerKeepaliveData{ - worker_id, activity_type, extra_data, timeout_type}; content::ServiceWorkerContext* service_worker_context = util::GetServiceWorkerContextForExtensionId(extension->id(), browser_context_); - service_worker_context->StartingExternalRequest(service_worker_version_id, - timeout_type, request_uuid); + content::ServiceWorkerExternalRequestResult start_result = + service_worker_context->StartingExternalRequest( + service_worker_version_id, timeout_type, request_uuid); + + service_worker_keepalives_[request_uuid] = ServiceWorkerKeepaliveData{ + worker_id, activity_type, extra_data, timeout_type, start_result}; + + base::UmaHistogramEnumeration( + "Extensions.ServiceWorkerBackground." + "ProcessManagerStartingExternalRequestResult", + start_result); + return request_uuid; } @@ -830,6 +839,8 @@ CHECK_EQ(iter->second.worker_id, worker_id); CHECK_EQ(iter->second.activity_type, activity_type); CHECK_EQ(iter->second.extra_data, extra_data); + content::ServiceWorkerExternalRequestResult start_result = + iter->second.start_result; service_worker_keepalives_.erase(iter); int64_t service_worker_version_id = worker_id.version_id; @@ -837,17 +848,29 @@ util::GetServiceWorkerContextForExtensionId(extension->id(), browser_context_); - content::ServiceWorkerExternalRequestResult result = + content::ServiceWorkerExternalRequestResult finish_result = service_worker_context->FinishedExternalRequest(service_worker_version_id, request_uuid); + if (start_result == content::ServiceWorkerExternalRequestResult::kOk) { + base::UmaHistogramEnumeration( + "Extensions.ServiceWorkerBackground." + "ProcessManagerFinishedExternalRequestResultWithSuccessfulStart", + finish_result); + } else { + base::UmaHistogramEnumeration( + "Extensions.ServiceWorkerBackground." + "ProcessManagerFinishedExternalRequestResultWithUnsuccessfulStart", + finish_result); + } + // Example of when kWorkerNotRunning can happen is when the renderer process // is killed while handling a service worker request (e.g. because of a bad // IPC message). - DCHECK((result == content::ServiceWorkerExternalRequestResult::kOk) || - (result == + DCHECK((finish_result == content::ServiceWorkerExternalRequestResult::kOk) || + (finish_result == content::ServiceWorkerExternalRequestResult::kWorkerNotRunning)) - << "; result = " << static_cast<int>(result); + << "; result = " << static_cast<int>(finish_result); } void ProcessManager::OnLazyBackgroundPageIdle(const std::string& extension_id,
diff --git a/extensions/browser/process_manager.h b/extensions/browser/process_manager.h index 128a704..83e5f63e 100644 --- a/extensions/browser/process_manager.h +++ b/extensions/browser/process_manager.h
@@ -23,6 +23,7 @@ #include "components/keyed_service/core/keyed_service.h" #include "content/public/browser/devtools_agent_host_observer.h" #include "content/public/browser/render_process_host_observer.h" +#include "content/public/browser/service_worker_external_request_result.h" #include "content/public/browser/service_worker_external_request_timeout_type.h" #include "extensions/browser/activity.h" #include "extensions/browser/event_page_tracker.h" @@ -63,10 +64,18 @@ // A struct representing an active service worker keepalive. struct ServiceWorkerKeepaliveData { + // The worker ID associated with the keepalive. WorkerId worker_id; + // The type of activity for the keepalive. Activity::Type activity_type; + // Any "additional data" for the keepalive; for instance, this could be + // the API function or event name. std::string extra_data; + // The timeout behavior for the given request. content::ServiceWorkerExternalRequestTimeoutType timeout_type; + // The result of trying to start an external request with the service + // worker layer. + content::ServiceWorkerExternalRequestResult start_result; }; using ServiceWorkerKeepaliveDataMap = std::map<base::Uuid, ServiceWorkerKeepaliveData>;
diff --git a/extensions/browser/test_extensions_browser_client.cc b/extensions/browser/test_extensions_browser_client.cc index d41d47a..61244441 100644 --- a/extensions/browser/test_extensions_browser_client.cc +++ b/extensions/browser/test_extensions_browser_client.cc
@@ -61,7 +61,7 @@ return false; } -bool TestExtensionsBrowserClient::IsValidContext(BrowserContext* context) { +bool TestExtensionsBrowserClient::IsValidContext(void* context) { return context == main_context_ || (incognito_context_ && context == incognito_context_); }
diff --git a/extensions/browser/test_extensions_browser_client.h b/extensions/browser/test_extensions_browser_client.h index cd4c1fd..3a35424a 100644 --- a/extensions/browser/test_extensions_browser_client.h +++ b/extensions/browser/test_extensions_browser_client.h
@@ -72,7 +72,7 @@ bool IsShuttingDown() override; bool AreExtensionsDisabled(const base::CommandLine& command_line, content::BrowserContext* context) override; - bool IsValidContext(content::BrowserContext* context) override; + bool IsValidContext(void* context) override; bool IsSameContext(content::BrowserContext* first, content::BrowserContext* second) override; bool HasOffTheRecordContext(content::BrowserContext* context) override;
diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc index 47699e9..c752abd 100644 --- a/extensions/common/manifest_constants.cc +++ b/extensions/common/manifest_constants.cc
@@ -58,6 +58,7 @@ const char kFileHandlerTypes[] = "types"; const char kFileHandlerVerb[] = "verb"; const char kGlobal[] = "global"; +const char kHandwritingLanguage[] = "handwriting_language"; const char kHideBookmarkButton[] = "hide_bookmark_button"; const char kHomepageURL[] = "homepage_url"; const char kHostPermissions[] = "host_permissions";
diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h index 6d3f487..9d28f1b 100644 --- a/extensions/common/manifest_constants.h +++ b/extensions/common/manifest_constants.h
@@ -60,6 +60,7 @@ extern const char kFileBrowserHandlerId[]; extern const char kFileBrowserHandlers[]; extern const char kGlobal[]; +extern const char kHandwritingLanguage[]; extern const char kHideBookmarkButton[]; extern const char kHomepageURL[]; extern const char kHostPermissions[];
diff --git a/extensions/shell/browser/shell_extensions_browser_client.cc b/extensions/shell/browser/shell_extensions_browser_client.cc index b931b542..ea62e42 100644 --- a/extensions/shell/browser/shell_extensions_browser_client.cc +++ b/extensions/shell/browser/shell_extensions_browser_client.cc
@@ -66,7 +66,7 @@ return false; } -bool ShellExtensionsBrowserClient::IsValidContext(BrowserContext* context) { +bool ShellExtensionsBrowserClient::IsValidContext(void* context) { DCHECK(browser_context_); return context == browser_context_; }
diff --git a/extensions/shell/browser/shell_extensions_browser_client.h b/extensions/shell/browser/shell_extensions_browser_client.h index 4b459b7..3e9732e 100644 --- a/extensions/shell/browser/shell_extensions_browser_client.h +++ b/extensions/shell/browser/shell_extensions_browser_client.h
@@ -41,7 +41,7 @@ bool IsShuttingDown() override; bool AreExtensionsDisabled(const base::CommandLine& command_line, content::BrowserContext* context) override; - bool IsValidContext(content::BrowserContext* context) override; + bool IsValidContext(void* context) override; bool IsSameContext(content::BrowserContext* first, content::BrowserContext* second) override; bool HasOffTheRecordContext(content::BrowserContext* context) override;
diff --git a/google_apis/gcm/tools/mcs_probe.cc b/google_apis/gcm/tools/mcs_probe.cc index f211b41..e3ad5d4 100644 --- a/google_apis/gcm/tools/mcs_probe.cc +++ b/google_apis/gcm/tools/mcs_probe.cc
@@ -66,7 +66,7 @@ #include "url/scheme_host_port.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif // This is a simple utility that initializes an mcs client and
diff --git a/gpu/command_buffer/tests/command_buffer_gles2_tests_main.cc b/gpu/command_buffer/tests/command_buffer_gles2_tests_main.cc index cf9ca9d..90414866 100644 --- a/gpu/command_buffer/tests/command_buffer_gles2_tests_main.cc +++ b/gpu/command_buffer/tests/command_buffer_gles2_tests_main.cc
@@ -7,7 +7,7 @@ #include "base/task/single_thread_task_executor.h" #include "build/build_config.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif #include "base/test/launcher/unit_test_launcher.h" #include "base/test/test_suite.h" @@ -61,7 +61,7 @@ base::TestSuite test_suite(argc, argv); #if BUILDFLAG(IS_MAC) - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif testing::InitGoogleMock(&argc, argv); return base::LaunchUnitTestsSerially(
diff --git a/gpu/command_buffer/tests/gl_tests_main.cc b/gpu/command_buffer/tests/gl_tests_main.cc index 2454ce06f..8553985 100644 --- a/gpu/command_buffer/tests/gl_tests_main.cc +++ b/gpu/command_buffer/tests/gl_tests_main.cc
@@ -16,7 +16,7 @@ #include "testing/gmock/include/gmock/gmock.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif namespace { @@ -44,7 +44,7 @@ GlTestsSuite gl_tests_suite(argc, argv); #if BUILDFLAG(IS_MAC) - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif testing::InitGoogleMock(&argc, argv); return base::LaunchUnitTestsSerially(
diff --git a/gpu/gles2_conform_support/gles2_conform_test.cc b/gpu/gles2_conform_support/gles2_conform_test.cc index a89fa0e..08b533b 100644 --- a/gpu/gles2_conform_support/gles2_conform_test.cc +++ b/gpu/gles2_conform_support/gles2_conform_test.cc
@@ -17,7 +17,7 @@ #include "base/logging.h" #include "build/build_config.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif #include "base/path_service.h" #include "base/process/launch.h" @@ -129,7 +129,7 @@ int main(int argc, char** argv) { base::CommandLine::Init(argc, argv); #if BUILDFLAG(IS_MAC) - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif ::testing::InitGoogleTest(&argc, argv); base::TestSuite test_suite(argc, argv);
diff --git a/gpu/gles2_conform_support/native/main.cc b/gpu/gles2_conform_support/native/main.cc index 55af6ed..97dd7bd 100644 --- a/gpu/gles2_conform_support/native/main.cc +++ b/gpu/gles2_conform_support/native/main.cc
@@ -10,7 +10,7 @@ #include "base/command_line.h" #include "build/build_config.h" #if BUILDFLAG(IS_MAC) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif #if BUILDFLAG(IS_WIN) #include "base/strings/utf_string_conversions.h" @@ -36,7 +36,7 @@ base::CommandLine::ForCurrentProcess()->GetArgs(); #if BUILDFLAG(IS_MAC) - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif std::unique_ptr<const char* []> argsArray(new const char*[args.size() + 1]); @@ -59,4 +59,3 @@ return 0; } -
diff --git "a/infra/config/generated/builders/reclient/Linux Builder reclient test \050casng\051 untrusted/properties.json" "b/infra/config/generated/builders/reclient/Linux Builder reclient test \050casng\051 untrusted/properties.json" new file mode 100644 index 0000000..10b9bbf0a --- /dev/null +++ "b/infra/config/generated/builders/reclient/Linux Builder reclient test \050casng\051 untrusted/properties.json"
@@ -0,0 +1,68 @@ +{ + "$build/chromium_tests_builder_config": { + "builder_config": { + "builder_db": { + "entries": [ + { + "builder_id": { + "bucket": "reclient", + "builder": "Linux Builder reclient test (casng) untrusted", + "project": "chromium" + }, + "builder_spec": { + "build_gs_bucket": "chromium-fyi-archive", + "builder_group": "chromium.reclient.fyi", + "execution_mode": "COMPILE_AND_TEST", + "legacy_chromium_config": { + "apply_configs": [ + "mb" + ], + "build_config": "Release", + "config": "chromium", + "target_bits": 64 + }, + "legacy_gclient_config": { + "apply_configs": [ + "use_clang_coverage", + "reclient_test" + ], + "config": "chromium" + } + } + } + ] + }, + "builder_ids": [ + { + "bucket": "reclient", + "builder": "Linux Builder reclient test (casng) untrusted", + "project": "chromium" + } + ] + } + }, + "$build/reclient": { + "bootstrap_env": { + "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "true", + "GOMA_DEPS_CACHE_TABLE_THRESHOLD": "40000", + "RBE_fast_log_collection": "true", + "RBE_use_casng": "true", + "RBE_use_unified_uploads": "true" + }, + "instance": "rbe-chromium-untrusted-test", + "metrics_project": "chromium-reclient-metrics", + "rewrapper_env": { + "RBE_exec_timeout": "2m" + }, + "scandeps_server": true + }, + "$recipe_engine/resultdb/test_presentation": { + "column_keys": [], + "grouping_keys": [ + "status", + "v.test_suite" + ] + }, + "builder_group": "chromium.reclient.fyi", + "recipe": "chromium" +} \ No newline at end of file
diff --git "a/infra/config/generated/builders/reclient/Linux Builder reclient test \050casng\051/properties.json" "b/infra/config/generated/builders/reclient/Linux Builder reclient test \050casng\051/properties.json" new file mode 100644 index 0000000..cc43315 --- /dev/null +++ "b/infra/config/generated/builders/reclient/Linux Builder reclient test \050casng\051/properties.json"
@@ -0,0 +1,68 @@ +{ + "$build/chromium_tests_builder_config": { + "builder_config": { + "builder_db": { + "entries": [ + { + "builder_id": { + "bucket": "reclient", + "builder": "Linux Builder reclient test (casng)", + "project": "chromium" + }, + "builder_spec": { + "build_gs_bucket": "chromium-fyi-archive", + "builder_group": "chromium.reclient.fyi", + "execution_mode": "COMPILE_AND_TEST", + "legacy_chromium_config": { + "apply_configs": [ + "mb" + ], + "build_config": "Release", + "config": "chromium", + "target_bits": 64 + }, + "legacy_gclient_config": { + "apply_configs": [ + "use_clang_coverage", + "reclient_test" + ], + "config": "chromium" + } + } + } + ] + }, + "builder_ids": [ + { + "bucket": "reclient", + "builder": "Linux Builder reclient test (casng)", + "project": "chromium" + } + ] + } + }, + "$build/reclient": { + "bootstrap_env": { + "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "true", + "GOMA_DEPS_CACHE_TABLE_THRESHOLD": "40000", + "RBE_fast_log_collection": "true", + "RBE_use_casng": "true", + "RBE_use_unified_uploads": "true" + }, + "instance": "rbe-chromium-trusted-test", + "metrics_project": "chromium-reclient-metrics", + "rewrapper_env": { + "RBE_exec_timeout": "2m" + }, + "scandeps_server": true + }, + "$recipe_engine/resultdb/test_presentation": { + "column_keys": [], + "grouping_keys": [ + "status", + "v.test_suite" + ] + }, + "builder_group": "chromium.reclient.fyi", + "recipe": "chromium" +} \ No newline at end of file
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg index 1ce9c54..b7839363 100644 --- a/infra/config/generated/luci/cr-buildbucket.cfg +++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -58762,6 +58762,176 @@ description_html: "Builds chromium using the test version of reclient and the rbe-chromium-trusted-test rbe instance." } builders { + name: "Linux Builder reclient test (casng)" + swarming_host: "chromium-swarm.appspot.com" + dimensions: "builderless:1" + dimensions: "cores:8" + dimensions: "cpu:x86-64" + dimensions: "free_space:standard" + dimensions: "os:Ubuntu-22.04" + dimensions: "pool:luci.chromium.ci" + dimensions: "ssd:0" + exe { + cipd_package: "infra/chromium/bootstrapper/${platform}" + cipd_version: "latest" + cmd: "bootstrapper" + } + properties: + '{' + ' "$bootstrap/exe": {' + ' "exe": {' + ' "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",' + ' "cipd_version": "refs/heads/main",' + ' "cmd": [' + ' "luciexe"' + ' ]' + ' }' + ' },' + ' "$bootstrap/properties": {' + ' "properties_file": "infra/config/generated/builders/reclient/Linux Builder reclient test (casng)/properties.json",' + ' "top_level_project": {' + ' "ref": "refs/heads/main",' + ' "repo": {' + ' "host": "chromium.googlesource.com",' + ' "project": "chromium/src"' + ' }' + ' }' + ' },' + ' "builder_group": "chromium.reclient.fyi",' + ' "led_builder_is_bootstrapped": true,' + ' "recipe": "chromium"' + '}' + execution_timeout_secs: 10800 + build_numbers: YES + service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "chromium_swarming.expose_merge_script_failures" + value: 100 + } + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + resultdb { + enable: true + bq_exports { + project: "chrome-luci-data" + dataset: "chromium" + table: "ci_test_results" + test_results {} + } + bq_exports { + project: "chrome-luci-data" + dataset: "chromium" + table: "gpu_ci_test_results" + test_results { + predicate { + test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+" + } + } + } + bq_exports { + project: "chrome-luci-data" + dataset: "chromium" + table: "blink_web_tests_ci_test_results" + test_results { + predicate { + test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)" + } + } + } + history_options { + use_invocation_timestamp: true + } + } + description_html: "Builds chromium using the test version of reclient and the rbe-chromium-trusted-test rbe instance." + } + builders { + name: "Linux Builder reclient test (casng) untrusted" + swarming_host: "chromium-swarm.appspot.com" + dimensions: "builderless:1" + dimensions: "cores:8" + dimensions: "cpu:x86-64" + dimensions: "free_space:standard" + dimensions: "os:Ubuntu-22.04" + dimensions: "pool:luci.chromium.ci" + dimensions: "ssd:0" + exe { + cipd_package: "infra/chromium/bootstrapper/${platform}" + cipd_version: "latest" + cmd: "bootstrapper" + } + properties: + '{' + ' "$bootstrap/exe": {' + ' "exe": {' + ' "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",' + ' "cipd_version": "refs/heads/main",' + ' "cmd": [' + ' "luciexe"' + ' ]' + ' }' + ' },' + ' "$bootstrap/properties": {' + ' "properties_file": "infra/config/generated/builders/reclient/Linux Builder reclient test (casng) untrusted/properties.json",' + ' "top_level_project": {' + ' "ref": "refs/heads/main",' + ' "repo": {' + ' "host": "chromium.googlesource.com",' + ' "project": "chromium/src"' + ' }' + ' }' + ' },' + ' "builder_group": "chromium.reclient.fyi",' + ' "led_builder_is_bootstrapped": true,' + ' "recipe": "chromium"' + '}' + execution_timeout_secs: 10800 + build_numbers: YES + service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "chromium_swarming.expose_merge_script_failures" + value: 100 + } + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + resultdb { + enable: true + bq_exports { + project: "chrome-luci-data" + dataset: "chromium" + table: "ci_test_results" + test_results {} + } + bq_exports { + project: "chrome-luci-data" + dataset: "chromium" + table: "gpu_ci_test_results" + test_results { + predicate { + test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+" + } + } + } + bq_exports { + project: "chrome-luci-data" + dataset: "chromium" + table: "blink_web_tests_ci_test_results" + test_results { + predicate { + test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)" + } + } + } + history_options { + use_invocation_timestamp: true + } + } + description_html: "Builds chromium using the test version of reclient and the rbe-chromium-untrusted-test rbe instance." + } + builders { name: "Linux Builder reclient test (unified uploads)" swarming_host: "chromium-swarm.appspot.com" dimensions: "builderless:1"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg index e3288d4..70c848a8 100644 --- a/infra/config/generated/luci/luci-milo.cfg +++ b/infra/config/generated/luci/luci-milo.cfg
@@ -14012,6 +14012,11 @@ short_name: "rcs" } builders { + name: "buildbucket/luci.chromium.reclient/Linux Builder reclient test (casng)" + category: "rbe|linux" + short_name: "rcs" + } + builders { name: "buildbucket/luci.chromium.reclient/Linux Builder reclient test (unified uploads)" category: "rbe|linux" short_name: "rcs" @@ -14072,6 +14077,11 @@ short_name: "rcs" } builders { + name: "buildbucket/luci.chromium.reclient/Linux Builder reclient test (casng) untrusted" + category: "rbecq|linux" + short_name: "rcs" + } + builders { name: "buildbucket/luci.chromium.reclient/Linux Builder reclient test (unified uploads) untrusted" category: "rbecq|linux" short_name: "rcs"
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg index 5e28414..e7c45db 100644 --- a/infra/config/generated/luci/luci-scheduler.cfg +++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -1555,6 +1555,24 @@ } } job { + id: "Linux Builder reclient test (casng)" + realm: "reclient" + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "reclient" + builder: "Linux Builder reclient test (casng)" + } +} +job { + id: "Linux Builder reclient test (casng) untrusted" + realm: "reclient" + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "reclient" + builder: "Linux Builder reclient test (casng) untrusted" + } +} +job { id: "Linux Builder reclient test (unified uploads)" realm: "reclient" buildbucket { @@ -6655,6 +6673,8 @@ triggers: "Linux Builder reclient staging" triggers: "Linux Builder reclient staging untrusted" triggers: "Linux Builder reclient test" + triggers: "Linux Builder reclient test (casng)" + triggers: "Linux Builder reclient test (casng) untrusted" triggers: "Linux Builder reclient test (unified uploads)" triggers: "Linux Builder reclient test (unified uploads) untrusted" triggers: "Linux Builder reclient test untrusted"
diff --git a/infra/config/subprojects/reclient/reclient.star b/infra/config/subprojects/reclient/reclient.star index c6262a7..378e7818 100644 --- a/infra/config/subprojects/reclient/reclient.star +++ b/infra/config/subprojects/reclient/reclient.star
@@ -192,6 +192,28 @@ }, ) +fyi_reclient_test_builder( + name = "Linux Builder reclient test (casng)", + builder_spec = builder_config.copy_from( + "ci/Linux Builder", + lambda spec: structs.evolve( + spec, + gclient_config = structs.extend( + spec.gclient_config, + apply_configs = [ + "reclient_test", + ], + ), + build_gs_bucket = "chromium-fyi-archive", + ), + ), + os = os.LINUX_DEFAULT, + console_view_category = "linux", + reclient_bootstrap_env = { + "RBE_use_casng": "true", + }, +) + fyi_reclient_staging_builder( name = "Mac Builder reclient staging", builder_spec = builder_config.copy_from(
diff --git a/ios/chrome/app/chrome_exe_main.mm b/ios/chrome/app/chrome_exe_main.mm index fef42a4..c8ac3fe 100644 --- a/ios/chrome/app/chrome_exe_main.mm +++ b/ios/chrome/app/chrome_exe_main.mm
@@ -61,18 +61,6 @@ } } -void SetUILanguageIfLanguageIsSelected() { - @autoreleasepool { - NSUserDefaults* standard_defaults = [NSUserDefaults standardUserDefaults]; - NSString* language = [standard_defaults valueForKey:@"UILanguageOverride"]; - if (!language || [language length] == 0) { - [standard_defaults removeObjectForKey:@"AppleLanguages"]; - } else { - [standard_defaults setObject:@[ language ] forKey:@"AppleLanguages"]; - } - } -} - int RunUIApplicationMain(int argc, char* argv[]) { @autoreleasepool { // Fetch the name of the UIApplication delegate stored in the application @@ -118,9 +106,6 @@ // Set NSUserDefaults keys to force pseudo-RTL if needed. SetTextDirectionIfPseudoRTLEnabled(); - // Set NSUserDefaults keys to force the UI language if needed. - SetUILanguageIfLanguageIsSelected(); - // Create this here since it's needed to start the crash handler. base::AtExitManager at_exit;
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm index 67d03403..76baab01 100644 --- a/ios/chrome/browser/flags/about_flags.mm +++ b/ios/chrome/browser/flags/about_flags.mm
@@ -926,12 +926,6 @@ flag_descriptions::kWaitThresholdMillisecondsForCapabilitiesApiDescription, flags_ui::kOsIos, MULTI_VALUE_TYPE(kWaitThresholdMillisecondsForCapabilitiesApiChoices)}, - {"autofill-fill-merchant-promo-code-fields", - flag_descriptions::kAutofillFillMerchantPromoCodeFieldsName, - flag_descriptions::kAutofillFillMerchantPromoCodeFieldsDescription, - flags_ui::kOsIos, - FEATURE_VALUE_TYPE( - autofill::features::kAutofillFillMerchantPromoCodeFields)}, {"new-overflow-menu", flag_descriptions::kNewOverflowMenuName, flag_descriptions::kNewOverflowMenuDescription, flags_ui::kOsIos, FEATURE_VALUE_TYPE(kNewOverflowMenu)}, @@ -1018,12 +1012,6 @@ flag_descriptions::kSendTabToSelfSigninPromoName, flag_descriptions::kSendTabToSelfSigninPromoDescription, flags_ui::kOsIos, FEATURE_VALUE_TYPE(send_tab_to_self::kSendTabToSelfSigninPromo)}, - {"autofill-enforce-delays-in-strike-database", - flag_descriptions::kAutofillEnforceDelaysInStrikeDatabaseName, - flag_descriptions::kAutofillEnforceDelaysInStrikeDatabaseDescription, - flags_ui::kOsIos, - FEATURE_VALUE_TYPE( - autofill::features::kAutofillEnforceDelaysInStrikeDatabase)}, {"autofill-upstream-allow-additional-email-domains", flag_descriptions::kAutofillUpstreamAllowAdditionalEmailDomainsName, flag_descriptions::kAutofillUpstreamAllowAdditionalEmailDomainsDescription,
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc index d3d22e93..f7209c9b 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -106,19 +106,6 @@ "When enabled, card product name (instead of issuer network) will be shown " "in Payments UI."; -const char kAutofillEnforceDelaysInStrikeDatabaseName[] = - "Enforce delay between offering Autofill opportunities in the strike " - "database"; -const char kAutofillEnforceDelaysInStrikeDatabaseDescription[] = - "When enabled, if previous Autofill feature offer was declined, " - "Chrome will wait for sometime before showing the offer again."; - -const char kAutofillFillMerchantPromoCodeFieldsName[] = - "Enable Autofill of promo code fields in forms"; -const char kAutofillFillMerchantPromoCodeFieldsDescription[] = - "When enabled, Autofill will attempt to fill merchant promo/coupon/gift " - "code fields when data is available."; - const char kAutofillIOSDelayBetweenFieldsName[] = "Autofill delay"; const char kAutofillIOSDelayBetweenFieldsDescription[] = "Delay between the different fields of a form being autofilled. In "
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h index 168e3d5e..51e4850 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -91,15 +91,6 @@ extern const char kAutofillEnableCardProductNameName[]; extern const char kAutofillEnableCardProductNameDescription[]; -// Title and description for flag to enforce delays between offering Autofill -// opportunities. -extern const char kAutofillEnforceDelaysInStrikeDatabaseName[]; -extern const char kAutofillEnforceDelaysInStrikeDatabaseDescription[]; - -// Title and description for the flag to fill promo code fields with Autofill. -extern const char kAutofillFillMerchantPromoCodeFieldsName[]; -extern const char kAutofillFillMerchantPromoCodeFieldsDescription[]; - // Title and description for the flag to control the autofill delay. extern const char kAutofillIOSDelayBetweenFieldsName[]; extern const char kAutofillIOSDelayBetweenFieldsDescription[];
diff --git a/ios/chrome/browser/memory/memory_metrics.cc b/ios/chrome/browser/memory/memory_metrics.cc index c356d789..61fafac 100644 --- a/ios/chrome/browser/memory/memory_metrics.cc +++ b/ios/chrome/browser/memory/memory_metrics.cc
@@ -11,8 +11,8 @@ #include <memory> +#include "base/apple/scoped_mach_port.h" #include "base/logging.h" -#include "base/mac/scoped_mach_port.h" #include "base/process/process_handle.h" #include "base/process/process_metrics.h" #include "build/build_config.h" @@ -37,7 +37,7 @@ uint64_t GetFreePhysicalBytes() { vm_statistics_data_t vmstat; mach_msg_type_number_t count = HOST_VM_INFO_COUNT; - base::mac::ScopedMachSendRight host(mach_host_self()); + base::apple::ScopedMachSendRight host(mach_host_self()); kern_return_t result = host_statistics( host.get(), HOST_VM_INFO, reinterpret_cast<host_info_t>(&vmstat), &count); if (result != KERN_SUCCESS) {
diff --git a/ios/chrome/browser/resources/Settings.bundle/Experimental.plist b/ios/chrome/browser/resources/Settings.bundle/Experimental.plist index c1152d3..af9cf66 100644 --- a/ios/chrome/browser/resources/Settings.bundle/Experimental.plist +++ b/ios/chrome/browser/resources/Settings.bundle/Experimental.plist
@@ -130,34 +130,6 @@ <key>Type</key> <string>PSMultiValueSpecifier</string> <key>Title</key> - <string>Force Language</string> - <key>Key</key> - <string>UILanguageOverride</string> - <key>DefaultValue</key> - <string></string> - <key>Values</key> - <array> - <string></string> - <string>ar</string> - <string>en</string> - <string>fr</string> - <string>he</string> - <string>hi</string> - </array> - <key>Titles</key> - <array> - <string>Default</string> - <string>Arabic</string> - <string>English</string> - <string>French</string> - <string>Hebrew</string> - <string>Hindi</string> - </array> - </dict> - <dict> - <key>Type</key> - <string>PSMultiValueSpecifier</string> - <key>Title</key> <string>Force Promo</string> <key>Key</key> <string>NextPromoForDisplayOverride</string>
diff --git a/ios/chrome/browser/sync/send_tab_to_self_sync_service_factory.mm b/ios/chrome/browser/sync/send_tab_to_self_sync_service_factory.mm index 1a20eea..17afd73 100644 --- a/ios/chrome/browser/sync/send_tab_to_self_sync_service_factory.mm +++ b/ios/chrome/browser/sync/send_tab_to_self_sync_service_factory.mm
@@ -36,7 +36,7 @@ return std::make_unique<SendTabToSelfSyncService>( GetChannel(), std::move(store_factory), history_service, - device_info_tracker); + browser_state->GetPrefs(), device_info_tracker); } // static
diff --git a/ios/chrome/browser/sync/sync_service_factory.mm b/ios/chrome/browser/sync/sync_service_factory.mm index eba86ca6..4547382 100644 --- a/ios/chrome/browser/sync/sync_service_factory.mm +++ b/ios/chrome/browser/sync/sync_service_factory.mm
@@ -15,6 +15,7 @@ #import "components/keyed_service/ios/browser_state_dependency_manager.h" #import "components/network_time/network_time_tracker.h" #import "components/prefs/pref_service.h" +#import "components/send_tab_to_self/send_tab_to_self_sync_service.h" #import "components/supervised_user/core/common/buildflags.h" #import "components/sync/base/command_line_switches.h" #import "components/sync/base/sync_util.h" @@ -49,6 +50,7 @@ #import "ios/chrome/browser/sync/ios_chrome_sync_client.h" #import "ios/chrome/browser/sync/ios_user_event_service_factory.h" #import "ios/chrome/browser/sync/model_type_store_service_factory.h" +#import "ios/chrome/browser/sync/send_tab_to_self_sync_service_factory.h" #import "ios/chrome/browser/sync/session_sync_service_factory.h" #import "ios/chrome/browser/sync/sync_invalidations_service_factory.h" #import "ios/chrome/browser/trusted_vault/ios_trusted_vault_service_factory.h" @@ -107,6 +109,7 @@ DependsOn(ChromeAccountManagerServiceFactory::GetInstance()); DependsOn(ConsentAuditorFactory::GetInstance()); DependsOn(DeviceInfoSyncServiceFactory::GetInstance()); + DependsOn(SendTabToSelfSyncServiceFactory::GetInstance()); // Sync needs this service to still be present when the sync engine is // disabled, so that preferences can be cleared. DependsOn(GoogleGroupsUpdaterServiceFactory::GetInstance()); @@ -207,5 +210,8 @@ browser_state->GetSyncablePrefs(); pref_service->OnSyncServiceInitialized(sync_service.get()); + SendTabToSelfSyncServiceFactory::GetForBrowserState(browser_state) + ->OnSyncServiceInitialized(sync_service.get()); + return sync_service; }
diff --git a/ios/chrome/browser/ui/autofill/bottom_sheet/payments_suggestion_bottom_sheet_egtest.mm b/ios/chrome/browser/ui/autofill/bottom_sheet/payments_suggestion_bottom_sheet_egtest.mm index 2eb8a34..784746049 100644 --- a/ios/chrome/browser/ui/autofill/bottom_sheet/payments_suggestion_bottom_sheet_egtest.mm +++ b/ios/chrome/browser/ui/autofill/bottom_sheet/payments_suggestion_bottom_sheet_egtest.mm
@@ -282,12 +282,11 @@ [ChromeEarlGreyUI waitForAppToIdle]; - [[EarlGrey selectElementWithMatcher: - grey_allOf(chrome_test_util::ButtonWithAccessibilityLabel( - l10n_util::GetNSString( - IDS_IOS_PAYMENT_BOTTOM_SHEET_SHOW_DETAILS)), - grey_interactable(), nullptr)] - performAction:grey_tap()]; + [[EarlGrey + selectElementWithMatcher: + grey_allOf(chrome_test_util::ContextMenuItemWithAccessibilityLabelId( + IDS_IOS_PAYMENT_BOTTOM_SHEET_SHOW_DETAILS), + grey_interactable(), nullptr)] performAction:grey_tap()]; [ChromeEarlGreyUI waitForAppToIdle];
diff --git a/ios/chrome/browser/ui/send_tab_to_self/send_tab_to_self_coordinator.mm b/ios/chrome/browser/ui/send_tab_to_self/send_tab_to_self_coordinator.mm index 1fe4bf3d..d65beba 100644 --- a/ios/chrome/browser/ui/send_tab_to_self/send_tab_to_self_coordinator.mm +++ b/ios/chrome/browser/ui/send_tab_to_self/send_tab_to_self_coordinator.mm
@@ -380,11 +380,10 @@ } - (absl::optional<send_tab_to_self::EntryPointDisplayReason>)displayReason { - ChromeBrowserState* browserState = self.browser->GetBrowserState(); - return send_tab_to_self::GetEntryPointDisplayReason( - _url, SyncServiceFactory::GetForBrowserState(browserState), - SendTabToSelfSyncServiceFactory::GetForBrowserState(browserState), - browserState->GetPrefs()); + send_tab_to_self::SendTabToSelfSyncService* service = + SendTabToSelfSyncServiceFactory::GetForBrowserState( + self.browser->GetBrowserState()); + return service ? service->GetEntryPointDisplayReason(_url) : absl::nullopt; } @end
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/BUILD.gn b/ios/chrome/browser/ui/settings/password/password_sharing/BUILD.gn index f643b50..0199f3d 100644 --- a/ios/chrome/browser/ui/settings/password/password_sharing/BUILD.gn +++ b/ios/chrome/browser/ui/settings/password/password_sharing/BUILD.gn
@@ -83,6 +83,7 @@ "//base/test:test_support", "//components/password_manager/core/browser", "//ios/chrome/app/strings", + "//ios/chrome/browser/shared/ui/symbols", "//ios/chrome/browser/shared/ui/table_view:test_support", "//ios/chrome/browser/shared/ui/table_view:utils", "//ios/chrome/browser/ui/settings/cells",
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/family_picker_view_controller.mm b/ios/chrome/browser/ui/settings/password/password_sharing/family_picker_view_controller.mm index 7c3e71e..b599d760a 100644 --- a/ios/chrome/browser/ui/settings/password/password_sharing/family_picker_view_controller.mm +++ b/ios/chrome/browser/ui/settings/password/password_sharing/family_picker_view_controller.mm
@@ -115,7 +115,9 @@ cell.textLabel.numberOfLines = 1; cell.detailTextLabel.numberOfLines = 1; if (_recipients[indexPath.row].isEligible) { - cell.accessoryView = [[UIImageView alloc] initWithImage:[self circleIcon]]; + cell.accessoryView = [[UIImageView alloc] + initWithImage:cell.isSelected ? [self checkmarkCircleIcon] + : [self circleIcon]]; } else { UIButton* infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight]; [infoButton setImage:[self infoCircleIcon] forState:UIControlStateNormal];
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/family_picker_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/password/password_sharing/family_picker_view_controller_unittest.mm index cfac1bc..6241a3d 100644 --- a/ios/chrome/browser/ui/settings/password/password_sharing/family_picker_view_controller_unittest.mm +++ b/ios/chrome/browser/ui/settings/password/password_sharing/family_picker_view_controller_unittest.mm
@@ -6,6 +6,7 @@ #import "base/strings/string_number_conversions.h" #import "components/password_manager/core/browser/sharing/recipients_fetcher.h" +#import "ios/chrome/browser/shared/ui/symbols/symbols.h" #import "ios/chrome/browser/shared/ui/table_view/chrome_table_view_controller_test.h" #import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h" #import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_item.h" @@ -18,7 +19,13 @@ namespace { +using password_manager::RecipientInfo; + +constexpr char kEmail[] = "test@gmail.com"; constexpr char kName[] = "user"; +constexpr char kPublicKey[] = "123456789"; +const uint32_t kPublicKeyVersion = 0; +const CGFloat kAccessorySymbolSize = 22; } // namespace @@ -40,6 +47,8 @@ const std::string num_str = base::NumberToString(i); recipient.email = "test" + num_str + "@gmail.com"; recipient.user_name = kName + num_str; + recipient.public_key.key = kPublicKey + num_str; + recipient.public_key.key_version = kPublicKeyVersion; [recipients addObject:([[RecipientInfoForIOSDisplay alloc] initWithRecipientInfo:recipient])]; } @@ -48,6 +57,59 @@ static_cast<FamilyPickerViewController*>(controller()); [family_controller setRecipients:recipients]; } + + void SetFamilyWithRecipients(const std::vector<RecipientInfo>& recipients) { + NSMutableArray<RecipientInfoForIOSDisplay*>* recipients_for_display = + [NSMutableArray array]; + + for (const RecipientInfo& recipient : recipients) { + [recipients_for_display addObject:([[RecipientInfoForIOSDisplay alloc] + initWithRecipientInfo:recipient])]; + } + + FamilyPickerViewController* family_controller = + static_cast<FamilyPickerViewController*>(controller()); + [family_controller setRecipients:recipients_for_display]; + } + + void CheckCellText(NSString* expected_text, int section, int row) { + SettingsImageDetailTextItem* item = + static_cast<SettingsImageDetailTextItem*>( + GetTableViewItem(section, row)); + EXPECT_NSEQ(expected_text, item.text); + } + + void CheckCellDetailText(NSString* expected_text, int section, int row) { + SettingsImageDetailTextItem* item = + static_cast<SettingsImageDetailTextItem*>( + GetTableViewItem(section, row)); + EXPECT_NSEQ(expected_text, item.detailText); + } + + void CheckCellAccessoryViewImage(UIImage* expected_image, + int section, + int row) { + UITableViewCell* cell = + [controller() tableView:controller().tableView + cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row + inSection:section]]; + ASSERT_TRUE([cell.accessoryView isKindOfClass:[UIImageView class]]); + EXPECT_TRUE( + [expected_image isEqual:((UIImageView*)cell.accessoryView).image]); + } + + void CheckCellAccessoryViewButton(UIImage* expected_button, + int section, + int row) { + UITableViewCell* cell = + [controller() tableView:controller().tableView + cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row + inSection:section]]; + ASSERT_TRUE([cell.accessoryView isKindOfClass:[UIButton class]]); + EXPECT_TRUE( + [expected_button isEqual:[((UIButton*)cell.accessoryView) + imageForState:UIControlStateNormal]]); + } }; TEST_F(FamilyPickerViewControllerTest, TestFamilyPickerLayout) { @@ -59,12 +121,58 @@ l10n_util::GetNSString(IDS_IOS_PASSWORD_SHARING_FAMILY_PICKER_SUBTITLE), 0); for (int i = 0; i < 5; i++) { - SettingsImageDetailTextItem* item = - static_cast<SettingsImageDetailTextItem*>( - GetTableViewItem(/*section=*/0, i)); - EXPECT_NSEQ(item.text, ([NSString stringWithFormat:@"%@%d", @"user", i])); - EXPECT_NSEQ( - item.detailText, - ([NSString stringWithFormat:@"%@%d%@", @"test", i, @"@gmail.com"])); + CheckCellText([NSString stringWithFormat:@"%@%d", @"user", i], 0, i); + CheckCellDetailText( + [NSString stringWithFormat:@"%@%d%@", @"test", i, @"@gmail.com"], 0, i); + CheckCellAccessoryViewImage( + DefaultSymbolWithPointSize(kCircleSymbol, kAccessorySymbolSize), 0, i); } } + +// Tests that ineligible password sharing recipient (without a public key) has +// an info button instead of a selectable checkmark icon. +TEST_F(FamilyPickerViewControllerTest, TestAccessoryViewOfIneligibleRecipient) { + RecipientInfo recipient; + recipient.email = kEmail; + recipient.user_name = kName; + recipient.public_key.key = ""; + SetFamilyWithRecipients({recipient}); + + EXPECT_EQ(NumberOfSections(), 1); + EXPECT_EQ(NumberOfItemsInSection(0), 1); + CheckCellText(@"user", 0, 0); + CheckCellDetailText(@"test@gmail.com", 0, 0); + CheckCellAccessoryViewButton( + DefaultSymbolWithPointSize(kInfoCircleSymbol, kAccessorySymbolSize), 0, + 0); +} + +// Tests accessory views on selecting and deselecting eligible password sharing +// recipient (with a public key). +TEST_F(FamilyPickerViewControllerTest, TestAccessoryViewOfEligibleRecipient) { + RecipientInfo recipient; + recipient.email = kEmail; + recipient.user_name = kName; + recipient.public_key.key = kPublicKey; + recipient.public_key.key_version = kPublicKeyVersion; + SetFamilyWithRecipients({recipient}); + + EXPECT_EQ(NumberOfSections(), 1); + EXPECT_EQ(NumberOfItemsInSection(0), 1); + CheckCellText(@"user", 0, 0); + CheckCellDetailText(@"test@gmail.com", 0, 0); + CheckCellAccessoryViewImage( + DefaultSymbolWithPointSize(kCircleSymbol, kAccessorySymbolSize), 0, 0); + + FamilyPickerViewController* family_controller = + static_cast<FamilyPickerViewController*>(controller()); + [family_controller tableView:family_controller.tableView + didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; + CheckCellAccessoryViewImage( + DefaultSymbolWithPointSize(kCircleSymbol, kAccessorySymbolSize), 0, 0); + + [family_controller tableView:family_controller.tableView + didDeselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; + CheckCellAccessoryViewImage( + DefaultSymbolWithPointSize(kCircleSymbol, kAccessorySymbolSize), 0, 0); +}
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/recipient_info.mm b/ios/chrome/browser/ui/settings/password/password_sharing/recipient_info.mm index bac923c..9f541ef 100644 --- a/ios/chrome/browser/ui/settings/password/password_sharing/recipient_info.mm +++ b/ios/chrome/browser/ui/settings/password/password_sharing/recipient_info.mm
@@ -15,6 +15,7 @@ if (self) { _fullName = base::SysUTF8ToNSString(recipient.user_name); _email = base::SysUTF8ToNSString(recipient.email); + _isEligible = !recipient.public_key.key.empty(); } return self; }
diff --git a/ios/chrome/browser/ui/sharing/activity_services/data/share_to_data_builder.mm b/ios/chrome/browser/ui/sharing/activity_services/data/share_to_data_builder.mm index c6e98f0..08c8cd4 100644 --- a/ios/chrome/browser/ui/sharing/activity_services/data/share_to_data_builder.mm +++ b/ios/chrome/browser/ui/sharing/activity_services/data/share_to_data_builder.mm
@@ -7,6 +7,7 @@ #import "base/check.h" #import "base/strings/sys_string_conversions.h" #import "components/send_tab_to_self/entry_point_display_reason.h" +#import "components/send_tab_to_self/send_tab_to_self_sync_service.h" #import "ios/chrome/browser/find_in_page/abstract_find_tab_helper.h" #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h" #import "ios/chrome/browser/shared/public/features/features.h" @@ -14,7 +15,6 @@ #import "ios/chrome/browser/signin/chrome_account_manager_service.h" #import "ios/chrome/browser/signin/chrome_account_manager_service_factory.h" #import "ios/chrome/browser/sync/send_tab_to_self_sync_service_factory.h" -#import "ios/chrome/browser/sync/sync_service_factory.h" #import "ios/chrome/browser/tabs/tab_title_util.h" #import "ios/chrome/browser/ui/sharing/activity_services/data/chrome_activity_item_thumbnail_generator.h" #import "ios/chrome/browser/ui/sharing/activity_services/data/share_to_data.h" @@ -26,6 +26,8 @@ namespace activity_services { +// TODO(crbug.com/1468530): Adopt consistent casing in these functions. + ShareToData* ShareToDataForWebState(web::WebState* web_state, const GURL& share_url) { CHECK(web_state); @@ -78,18 +80,15 @@ ChromeBrowserState::FromBrowserState(web_state->GetBrowserState()); ChromeAccountManagerService* accountManagerService = ChromeAccountManagerServiceFactory::GetForBrowserState(browser_state); + send_tab_to_self::SendTabToSelfSyncService* send_tab_to_self_service = + SendTabToSelfSyncServiceFactory::GetForBrowserState(browser_state); // When there are no device-level accounts, it's only possible to show the // promo UI if IsConsistencyNewAccountInterfaceEnabled() is true. BOOL can_send_tab_to_self = - !browser_state->IsOffTheRecord() && (accountManagerService->HasIdentities() || IsConsistencyNewAccountInterfaceEnabled()) && - send_tab_to_self::GetEntryPointDisplayReason( - finalURLToShare, - SyncServiceFactory::GetForBrowserState(browser_state), - SendTabToSelfSyncServiceFactory::GetForBrowserState(browser_state), - browser_state->GetPrefs()) - .has_value(); + send_tab_to_self_service && + send_tab_to_self_service->GetEntryPointDisplayReason(finalURLToShare); return [[ShareToData alloc] initWithShareURL:finalURLToShare visibleURL:web_state->GetVisibleURL()
diff --git a/ios/chrome/test/block_cleanup_test.h b/ios/chrome/test/block_cleanup_test.h index 7507272d..7a1151bb 100644 --- a/ios/chrome/test/block_cleanup_test.h +++ b/ios/chrome/test/block_cleanup_test.h
@@ -9,7 +9,7 @@ #import <memory> -#import "base/mac/scoped_nsautorelease_pool.h" +#import "base/apple/scoped_nsautorelease_pool.h" #import "testing/platform_test.h" // Extends PlatformTest to provide a TearDown() method that spins the runloop @@ -27,7 +27,7 @@ void SpinRunLoop(NSTimeInterval cleanup_time); private: - std::unique_ptr<base::mac::ScopedNSAutoreleasePool> pool_; + std::unique_ptr<base::apple::ScopedNSAutoreleasePool> pool_; }; #endif // IOS_CHROME_TEST_BLOCK_CLEANUP_TEST_H_
diff --git a/ios/chrome/test/block_cleanup_test.mm b/ios/chrome/test/block_cleanup_test.mm index 8211332..fbf0499d 100644 --- a/ios/chrome/test/block_cleanup_test.mm +++ b/ios/chrome/test/block_cleanup_test.mm
@@ -6,15 +6,15 @@ #import <memory> +#import "base/apple/scoped_nsautorelease_pool.h" #import "base/check.h" -#import "base/mac/scoped_nsautorelease_pool.h" #import "ios/chrome/test/block_cleanup_test.h" BlockCleanupTest::BlockCleanupTest() = default; BlockCleanupTest::~BlockCleanupTest() = default; void BlockCleanupTest::SetUp() { - pool_ = std::make_unique<base::mac::ScopedNSAutoreleasePool>(); + pool_ = std::make_unique<base::apple::ScopedNSAutoreleasePool>(); } void BlockCleanupTest::TearDown() {
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 index 99462f2..cfeb6e3b 100644 --- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 +++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@ -aae9dab1f5dcb9e1134ce60cab5b0461662d8340 \ No newline at end of file +a8f89d5d71ce11da137b346bb8904e7685a5fc09 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 index ef3055d5..114756f 100644 --- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 +++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@ -18afc82fa70fb307c7c45b0f21786aebf4e275d5 \ No newline at end of file +61d89814fed3dbc44539c5b4c1fe4a914d3f4ba5 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 index 9a94600..37675031 100644 --- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@ -7286af6d698cd769abf220f6302fdf698d07a081 \ No newline at end of file +8db8c4572d26a92caa0a332fa2ec33daa3f4f98a \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 index d3b9ed25..d18604c4 100644 --- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@ -bf87be49f93f507b2f34dd461fa978e57f836638 \ No newline at end of file +119101910619da485d7ac83bdb3b59d6eca04377 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 index fc7bbd30..1937ab6 100644 --- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@ -fbdce2e67830dd550caeb236d8353ae530901cbc \ No newline at end of file +bcba720dfd41ff34a84a1114cbc875630ecc1fe8 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 index f113761a..68c8d51e 100644 --- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@ -bd83891efd1a1254b2e729c1920ad7f9c3554c58 \ No newline at end of file +ee6b5f65055952363ac5605786e59acb3f946878 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 index f694dc0..e8c7f44 100644 --- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@ -52c69796c1162dea61017fa30bcf41b59f43eab7 \ No newline at end of file +05a508a49a8202f9fbbb3a905ee81990c7f59e2f \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 index 1004e69..65ce281 100644 --- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@ -a32c0eef147a2ca5a610fb05f9bb0f1d9395bf03 \ No newline at end of file +b1061969fbb30406cb0e140672a37d734801d7cc \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 index 321fac0..7a3ccf5 100644 --- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@ -46fde372cd0cf9a71819dddef96abd0a86810129 \ No newline at end of file +063bbbbb1dff1b0096c1099a8945c3cdefcc69be \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 index 20ce9ab..6422c620 100644 --- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@ -a78e47e9d45b3c71a6ab9cb70019fb0c08fcb455 \ No newline at end of file +f48f1d166b734834f0805f2e3f37ec20676706c8 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 index 94705f6..869c250 100644 --- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 +++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@ -6d8fc40a03b7c5c142d9eaeb4263ccd177b76ca4 \ No newline at end of file +1ecdaba5d90c746b92c46246576b5cc042a40943 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 index 32e8e91..20bd1929 100644 --- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 +++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@ -69dbf86ba67a3f14f1294b6cf32fc1ec220028a0 \ No newline at end of file +cdbf8cad52a65f8351c28bd91280b8af1a0399c6 \ No newline at end of file
diff --git a/ipc/ipc_message_utils.cc b/ipc/ipc_message_utils.cc index 252e01a1..4607983 100644 --- a/ipc/ipc_message_utils.cc +++ b/ipc/ipc_message_utils.cc
@@ -956,7 +956,7 @@ zx::vmo vmo = const_cast<param_type&>(p).PassPlatformHandle(); WriteParam(m, vmo); #elif BUILDFLAG(IS_APPLE) - base::mac::ScopedMachSendRight h = + base::apple::ScopedMachSendRight h = const_cast<param_type&>(p).PassPlatformHandle(); MachPortMac mach_port_mac(h.get()); WriteParam(m, mach_port_mac); @@ -1014,8 +1014,8 @@ if (!ReadParam(m, iter, &mach_port_mac)) return false; *r = base::subtle::PlatformSharedMemoryRegion::Take( - base::mac::ScopedMachSendRight(mach_port_mac.get_mach_port()), mode, size, - guid); + base::apple::ScopedMachSendRight(mach_port_mac.get_mach_port()), mode, + size, guid); #elif BUILDFLAG(IS_POSIX) scoped_refptr<base::Pickle::Attachment> attachment; if (!m->ReadAttachment(iter, &attachment))
diff --git a/ipc/mach_port_attachment_mac.cc b/ipc/mach_port_attachment_mac.cc index 478a098..0fb9c62 100644 --- a/ipc/mach_port_attachment_mac.cc +++ b/ipc/mach_port_attachment_mac.cc
@@ -6,7 +6,7 @@ #include <stdint.h> -#include "base/mac/mach_logging.h" +#include "base/apple/mach_logging.h" namespace IPC { namespace internal {
diff --git a/media/audio/mac/audio_low_latency_input_mac.cc b/media/audio/mac/audio_low_latency_input_mac.cc index 1bc1187..8b97419e 100644 --- a/media/audio/mac/audio_low_latency_input_mac.cc +++ b/media/audio/mac/audio_low_latency_input_mac.cc
@@ -10,12 +10,12 @@ #include <string> #include "base/apple/osstatus_logging.h" +#include "base/apple/scoped_mach_port.h" #include "base/functional/bind.h" #include "base/logging.h" #include "base/mac/foundation_util.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_cftyperef.h" -#include "base/mac/scoped_mach_port.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/strings/strcat.h"
diff --git a/mojo/core/channel.cc b/mojo/core/channel.cc index 497a9761..1495bce 100644 --- a/mojo/core/channel.cc +++ b/mojo/core/channel.cc
@@ -29,7 +29,7 @@ #include "mojo/core/embedder/features.h" #if BUILDFLAG(MOJO_USE_APPLE_CHANNEL) -#include "base/mac/mach_logging.h" +#include "base/apple/mach_logging.h" #elif BUILDFLAG(IS_WIN) #include "base/win/win_util.h" #endif
diff --git a/mojo/core/channel_mac.cc b/mojo/core/channel_mac.cc index 229ff07..12e62877 100644 --- a/mojo/core/channel_mac.cc +++ b/mojo/core/channel_mac.cc
@@ -14,15 +14,15 @@ #include <utility> #include <vector> +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" +#include "base/apple/scoped_mach_vm.h" #include "base/containers/buffer_iterator.h" #include "base/containers/circular_deque.h" #include "base/containers/span.h" #include "base/functional/bind.h" #include "base/logging.h" -#include "base/mac/mach_logging.h" #include "base/mac/scoped_mach_msg_destroy.h" -#include "base/mac/scoped_mach_port.h" -#include "base/mac/scoped_mach_vm.h" #include "base/message_loop/message_pump_for_io.h" #include "base/task/current_thread.h" #include "base/task/single_thread_task_runner.h" @@ -197,8 +197,8 @@ // establishes the bidirectional communication channel. if (send_port_ != MACH_PORT_NULL) { DCHECK(receive_port_ == MACH_PORT_NULL); - CHECK(base::mac::CreateMachPort(&receive_port_, nullptr, - MACH_PORT_QLIMIT_LARGE)); + CHECK(base::apple::CreateMachPort(&receive_port_, nullptr, + MACH_PORT_QLIMIT_LARGE)); if (!RequestSendDeadNameNotification()) { OnError(Error::kConnectionFailed); return; @@ -245,11 +245,11 @@ // connected to |send_port_| becomes a dead name. This should be called as // soon as the Channel establishes both the send and receive ports. bool RequestSendDeadNameNotification() { - base::mac::ScopedMachSendRight previous; + base::apple::ScopedMachSendRight previous; kern_return_t kr = mach_port_request_notification( mach_task_self(), send_port_.get(), MACH_NOTIFY_DEAD_NAME, 0, receive_port_.get(), MACH_MSG_TYPE_MAKE_SEND_ONCE, - base::mac::ScopedMachSendRight::Receiver(previous).get()); + base::apple::ScopedMachSendRight::Receiver(previous).get()); if (kr != KERN_SUCCESS) { // If port is already a dead name (i.e. the receiver is already gone), // then the channel should be shut down by the caller. @@ -306,7 +306,7 @@ return false; } - send_port_ = base::mac::ScopedMachSendRight(message->msgh_remote_port); + send_port_ = base::apple::ScopedMachSendRight(message->msgh_remote_port); if (!RequestSendDeadNameNotification()) { send_port_.reset(); @@ -559,7 +559,7 @@ sizeof(audit_token_t)) == 0) { DCHECK(notification->not_port == send_port_); // Release the notification's send right using this scoper. - base::mac::ScopedMachSendRight notify_port(notification->not_port); + base::apple::ScopedMachSendRight notify_port(notification->not_port); } OnError(Error::kDisconnected); return; @@ -633,12 +633,12 @@ switch (descriptor.disposition) { case MACH_MSG_TYPE_MOVE_SEND: incoming_handles_.emplace_back( - base::mac::ScopedMachSendRight(descriptor.name)); + base::apple::ScopedMachSendRight(descriptor.name)); descriptor.name = MACH_PORT_NULL; break; case MACH_MSG_TYPE_MOVE_RECEIVE: incoming_handles_.emplace_back( - base::mac::ScopedMachReceiveRight(descriptor.name)); + base::apple::ScopedMachReceiveRight(descriptor.name)); descriptor.name = MACH_PORT_NULL; break; default: @@ -650,7 +650,7 @@ } base::span<const char> payload; - base::mac::ScopedMachVM ool_memory; + base::apple::ScopedMachVM ool_memory; if (transfer_message_ool) { auto* descriptor = buffer.Object<mach_msg_ool_descriptor_t>(); if (descriptor->type != MACH_MSG_OOL_DESCRIPTOR) { @@ -698,8 +698,8 @@ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; - base::mac::ScopedMachReceiveRight receive_port_; - base::mac::ScopedMachSendRight send_port_; + base::apple::ScopedMachReceiveRight receive_port_; + base::apple::ScopedMachSendRight send_port_; // Whether to leak the above Mach ports when the channel is shut down. bool leak_handles_ = false; @@ -716,7 +716,7 @@ std::unique_ptr<audit_token_t> peer_audit_token_; // IO buffer for receiving Mach messages. Only accessed on |io_task_runner_|. - base::mac::ScopedMachVM receive_buffer_; + base::apple::ScopedMachVM receive_buffer_; // Handles that were received with a message that are validated and returned // in GetReadPlatformHandles(). Only accessed on |io_task_runner_|. @@ -732,7 +732,7 @@ // shutdown. bool reject_writes_ GUARDED_BY(write_lock_) = false; // IO buffer for sending Mach messages. - base::mac::ScopedMachVM send_buffer_ GUARDED_BY(write_lock_); + base::apple::ScopedMachVM send_buffer_ GUARDED_BY(write_lock_); // If a message timed out during send in MachMessageSendLocked(), this will // be true to indicate that |send_buffer_| contains a message that must // be sent. If this is true, then other calls to Write() queue messages onto
diff --git a/mojo/core/channel_mac_fuzzer.cc b/mojo/core/channel_mac_fuzzer.cc index 7f5f35d5..5a09620 100644 --- a/mojo/core/channel_mac_fuzzer.cc +++ b/mojo/core/channel_mac_fuzzer.cc
@@ -4,8 +4,8 @@ #include <list> +#include "base/apple/mach_logging.h" #include "base/logging.h" -#include "base/mac/mach_logging.h" #include "base/message_loop/message_pump_type.h" #include "base/run_loop.h" #include "base/task/single_thread_task_executor.h"
diff --git a/mojo/core/channel_unittest.cc b/mojo/core/channel_unittest.cc index f855528b..6bb1cd3 100644 --- a/mojo/core/channel_unittest.cc +++ b/mojo/core/channel_unittest.cc
@@ -649,7 +649,7 @@ get_send_name_refs(); EXPECT_EQ(2u, send); EXPECT_EQ(0u, dead); - base::mac::ScopedMachSendRight extra_send(send_name); + base::apple::ScopedMachSendRight extra_send(send_name); // Channel A gets created with the Mach send right from |platform_channel|. CallbackChannelDelegate delegate_a;
diff --git a/mojo/core/ipcz_driver/wrapped_platform_handle.cc b/mojo/core/ipcz_driver/wrapped_platform_handle.cc index 65c5b33..8f15cedc 100644 --- a/mojo/core/ipcz_driver/wrapped_platform_handle.cc +++ b/mojo/core/ipcz_driver/wrapped_platform_handle.cc
@@ -24,8 +24,8 @@ #if BUILDFLAG(IS_APPLE) #include <mach/mach.h> -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_mach_port.h" +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" #endif namespace mojo::core::ipcz_driver { @@ -92,9 +92,9 @@ } // extern "C" PlatformHandle MakeFDTransmissible(base::ScopedFD fd) { - base::mac::ScopedMachSendRight port; + base::apple::ScopedMachSendRight port; kern_return_t kr = fileport_makeport( - fd.get(), base::mac::ScopedMachSendRight::Receiver(port).get()); + fd.get(), base::apple::ScopedMachSendRight::Receiver(port).get()); if (kr != KERN_SUCCESS) { MACH_LOG(ERROR, kr) << "fileport_makeport"; return {};
diff --git a/mojo/core/platform_handle_utils.cc b/mojo/core/platform_handle_utils.cc index 18ef06c..e95b2cb 100644 --- a/mojo/core/platform_handle_utils.cc +++ b/mojo/core/platform_handle_utils.cc
@@ -18,7 +18,7 @@ #endif #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_mach_port.h" +#include "base/apple/scoped_mach_port.h" #endif namespace mojo {
diff --git a/mojo/core/platform_wrapper_unittest.cc b/mojo/core/platform_wrapper_unittest.cc index 112b147..82372876 100644 --- a/mojo/core/platform_wrapper_unittest.cc +++ b/mojo/core/platform_wrapper_unittest.cc
@@ -25,7 +25,7 @@ #include <windows.h> #include "base/win/scoped_handle.h" #elif BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_mach_port.h" +#include "base/apple/scoped_mach_port.h" #endif #if BUILDFLAG(IS_WIN) @@ -214,8 +214,8 @@ auto platform_handle = zx::vmo(static_cast<zx_handle_t>(os_buffer.value)); #elif BUILDFLAG(IS_APPLE) ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT, os_buffer.type); - auto platform_handle = - base::mac::ScopedMachSendRight(static_cast<mach_port_t>(os_buffer.value)); + auto platform_handle = base::apple::ScopedMachSendRight( + static_cast<mach_port_t>(os_buffer.value)); #elif BUILDFLAG(IS_POSIX) ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR, os_buffer.type); auto platform_handle = base::ScopedFD(static_cast<int>(os_buffer.value));
diff --git a/mojo/public/cpp/platform/named_platform_channel_mac.cc b/mojo/public/cpp/platform/named_platform_channel_mac.cc index 6996843..b1505ac 100644 --- a/mojo/public/cpp/platform/named_platform_channel_mac.cc +++ b/mojo/public/cpp/platform/named_platform_channel_mac.cc
@@ -7,9 +7,9 @@ #include <mach/port.h> #include <servers/bootstrap.h> +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" #include "base/mac/foundation_util.h" -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_mach_port.h" #include "base/rand_util.h" #include "base/strings/stringprintf.h" #include "mojo/public/cpp/platform/platform_channel.h" @@ -36,10 +36,10 @@ DCHECK_LT(bootstrap_name.length(), static_cast<size_t>(BOOTSTRAP_MAX_NAME_LEN)); - base::mac::ScopedMachReceiveRight receive_right; + base::apple::ScopedMachReceiveRight receive_right; kern_return_t kr = bootstrap_check_in( bootstrap_port, bootstrap_name.c_str(), - base::mac::ScopedMachReceiveRight::Receiver(receive_right).get()); + base::apple::ScopedMachReceiveRight::Receiver(receive_right).get()); if (kr != KERN_SUCCESS) { BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_check_in " << bootstrap_name; return PlatformChannelServerEndpoint(); @@ -61,10 +61,10 @@ // static PlatformChannelEndpoint NamedPlatformChannel::CreateClientEndpoint( const Options& options) { - base::mac::ScopedMachSendRight send_right; + base::apple::ScopedMachSendRight send_right; kern_return_t kr = bootstrap_look_up( bootstrap_port, options.server_name.c_str(), - base::mac::ScopedMachSendRight::Receiver(send_right).get()); + base::apple::ScopedMachSendRight::Receiver(send_right).get()); if (kr != KERN_SUCCESS) { BOOTSTRAP_VLOG(1, kr) << "bootstrap_look_up " << options.server_name; return PlatformChannelEndpoint();
diff --git a/mojo/public/cpp/platform/platform_channel.cc b/mojo/public/cpp/platform/platform_channel.cc index 83bf6f8..ce0a3a8 100644 --- a/mojo/public/cpp/platform/platform_channel.cc +++ b/mojo/public/cpp/platform/platform_channel.cc
@@ -40,8 +40,8 @@ #if BUILDFLAG(MOJO_USE_APPLE_CHANNEL) #include <mach/port.h> -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_mach_port.h" +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" #endif #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL) @@ -110,11 +110,11 @@ // handshake with its peer to establish two sets of Mach receive and send // rights. The handshake process starts with the creation of one // PlatformChannel endpoint. - base::mac::ScopedMachReceiveRight receive; - base::mac::ScopedMachSendRight send; + base::apple::ScopedMachReceiveRight receive; + base::apple::ScopedMachSendRight send; // The mpl_qlimit specified here should stay in sync with // NamedPlatformChannel. - CHECK(base::mac::CreateMachPort(&receive, &send, MACH_PORT_QLIMIT_LARGE)); + CHECK(base::apple::CreateMachPort(&receive, &send, MACH_PORT_QLIMIT_LARGE)); // In a reverse of Mach messaging semantics, in Mojo the "local" endpoint is // the send right, while the "remote" end is the receive right.
diff --git a/mojo/public/cpp/platform/platform_channel_endpoint.cc b/mojo/public/cpp/platform/platform_channel_endpoint.cc index 15f90cd..7d181c3 100644 --- a/mojo/public/cpp/platform/platform_channel_endpoint.cc +++ b/mojo/public/cpp/platform/platform_channel_endpoint.cc
@@ -18,8 +18,8 @@ #if BUILDFLAG(MOJO_USE_APPLE_CHANNEL) #include <mach/port.h> +#include "base/apple/scoped_mach_port.h" #include "base/mac/mach_port_rendezvous.h" -#include "base/mac/scoped_mach_port.h" #elif BUILDFLAG(IS_FUCHSIA) #include <lib/zx/handle.h> #elif BUILDFLAG(IS_POSIX) @@ -102,7 +102,7 @@ value = base::NumberToString(mapped_fd); #elif BUILDFLAG(MOJO_USE_APPLE_CHANNEL) DCHECK(platform_handle().is_mach_receive()); - base::mac::ScopedMachReceiveRight receive_right = + base::apple::ScopedMachReceiveRight receive_right = TakePlatformHandle().TakeMachReceiveRight(); base::MachPortsForRendezvous::key_type rendezvous_key = 0; do {
diff --git a/mojo/public/cpp/platform/platform_handle.cc b/mojo/public/cpp/platform/platform_handle.cc index 07fb85e..02f75171 100644 --- a/mojo/public/cpp/platform/platform_handle.cc +++ b/mojo/public/cpp/platform/platform_handle.cc
@@ -22,8 +22,8 @@ #elif BUILDFLAG(IS_APPLE) #include <mach/vm_map.h> -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_mach_port.h" +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" #endif #if BUILDFLAG(IS_POSIX) @@ -72,17 +72,17 @@ return std::move(dupe); } #elif BUILDFLAG(IS_APPLE) -base::mac::ScopedMachSendRight CloneMachPort( - const base::mac::ScopedMachSendRight& mach_port) { +base::apple::ScopedMachSendRight CloneMachPort( + const base::apple::ScopedMachSendRight& mach_port) { DCHECK(mach_port.is_valid()); kern_return_t kr = mach_port_mod_refs(mach_task_self(), mach_port.get(), MACH_PORT_RIGHT_SEND, 1); if (kr != KERN_SUCCESS) { MACH_DLOG(ERROR, kr) << "mach_port_mod_refs"; - return base::mac::ScopedMachSendRight(); + return base::apple::ScopedMachSendRight(); } - return base::mac::ScopedMachSendRight(mach_port.get()); + return base::apple::ScopedMachSendRight(mach_port.get()); } #endif @@ -108,9 +108,9 @@ PlatformHandle::PlatformHandle(zx::handle handle) : type_(Type::kHandle), handle_(std::move(handle)) {} #elif BUILDFLAG(IS_APPLE) -PlatformHandle::PlatformHandle(base::mac::ScopedMachSendRight mach_port) +PlatformHandle::PlatformHandle(base::apple::ScopedMachSendRight mach_port) : type_(Type::kMachSend), mach_send_(std::move(mach_port)) {} -PlatformHandle::PlatformHandle(base::mac::ScopedMachReceiveRight mach_port) +PlatformHandle::PlatformHandle(base::apple::ScopedMachReceiveRight mach_port) : type_(Type::kMachReceive), mach_receive_(std::move(mach_port)) {} #endif @@ -210,10 +210,10 @@ return PlatformHandle(zx::handle(handle->value)); #elif BUILDFLAG(IS_APPLE) if (handle->type == MOJO_PLATFORM_HANDLE_TYPE_MACH_SEND_RIGHT) { - return PlatformHandle(base::mac::ScopedMachSendRight( + return PlatformHandle(base::apple::ScopedMachSendRight( static_cast<mach_port_t>(handle->value))); } else if (handle->type == MOJO_PLATFORM_HANDLE_TYPE_MACH_RECEIVE_RIGHT) { - return PlatformHandle(base::mac::ScopedMachReceiveRight( + return PlatformHandle(base::apple::ScopedMachReceiveRight( static_cast<mach_port_t>(handle->value))); } #endif
diff --git a/mojo/public/cpp/platform/platform_handle.h b/mojo/public/cpp/platform/platform_handle.h index 315396d..7a6547d0 100644 --- a/mojo/public/cpp/platform/platform_handle.h +++ b/mojo/public/cpp/platform/platform_handle.h
@@ -16,7 +16,7 @@ #elif BUILDFLAG(IS_FUCHSIA) #include <lib/zx/handle.h> #elif BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_mach_port.h" +#include "base/apple/scoped_mach_port.h" #endif #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) @@ -62,8 +62,8 @@ #elif BUILDFLAG(IS_FUCHSIA) explicit PlatformHandle(zx::handle handle); #elif BUILDFLAG(IS_APPLE) - explicit PlatformHandle(base::mac::ScopedMachSendRight mach_port); - explicit PlatformHandle(base::mac::ScopedMachReceiveRight mach_port); + explicit PlatformHandle(base::apple::ScopedMachSendRight mach_port); + explicit PlatformHandle(base::apple::ScopedMachReceiveRight mach_port); #endif #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) @@ -140,10 +140,10 @@ bool is_valid_mach_send() const { return mach_send_.is_valid(); } bool is_mach_send() const { return type_ == Type::kMachSend; } - const base::mac::ScopedMachSendRight& GetMachSendRight() const { + const base::apple::ScopedMachSendRight& GetMachSendRight() const { return mach_send_; } - base::mac::ScopedMachSendRight TakeMachSendRight() { + base::apple::ScopedMachSendRight TakeMachSendRight() { if (type_ == Type::kMachSend) type_ = Type::kNone; return std::move(mach_send_); @@ -154,10 +154,10 @@ bool is_valid_mach_receive() const { return mach_receive_.is_valid(); } bool is_mach_receive() const { return type_ == Type::kMachReceive; } - const base::mac::ScopedMachReceiveRight& GetMachReceiveRight() const { + const base::apple::ScopedMachReceiveRight& GetMachReceiveRight() const { return mach_receive_; } - base::mac::ScopedMachReceiveRight TakeMachReceiveRight() { + base::apple::ScopedMachReceiveRight TakeMachReceiveRight() { if (type_ == Type::kMachReceive) type_ = Type::kNone; return std::move(mach_receive_); @@ -223,8 +223,8 @@ #elif BUILDFLAG(IS_FUCHSIA) zx::handle handle_; #elif BUILDFLAG(IS_APPLE) - base::mac::ScopedMachSendRight mach_send_; - base::mac::ScopedMachReceiveRight mach_receive_; + base::apple::ScopedMachSendRight mach_send_; + base::apple::ScopedMachReceiveRight mach_receive_; #endif #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
diff --git a/net/BUILD.gn b/net/BUILD.gn index a57b486..1a2ebcc 100644 --- a/net/BUILD.gn +++ b/net/BUILD.gn
@@ -3349,8 +3349,8 @@ "//net", ] data = [ - "fuzzer_data", - "fuzzer_dictionaries", + "data/fuzzer_data", + "data/fuzzer_dictionaries", ] allow_circular_includes_from = [ "//net/dns:fuzzer_test_support" ] }
diff --git a/net/base/features.cc b/net/base/features.cc index 5380190..17a3332 100644 --- a/net/base/features.cc +++ b/net/base/features.cc
@@ -261,7 +261,7 @@ BASE_FEATURE(kBlockTruncatedCookies, "BlockTruncatedCookies", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kStaticKeyPinningEnforcement, "StaticKeyPinningEnforcement",
diff --git a/net/base/upload_file_element_reader_unittest.cc b/net/base/upload_file_element_reader_unittest.cc index f408a6111..c00f72dc 100644 --- a/net/base/upload_file_element_reader_unittest.cc +++ b/net/base/upload_file_element_reader_unittest.cc
@@ -22,7 +22,7 @@ #include "testing/gtest/include/gtest/gtest.h" #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif using net::test::IsError; @@ -96,7 +96,7 @@ #if BUILDFLAG(IS_APPLE) // May be needed to avoid leaks on OSX. - base::mac::ScopedNSAutoreleasePool scoped_pool_; + base::apple::ScopedNSAutoreleasePool scoped_pool_; #endif std::vector<char> bytes_;
diff --git a/net/cert/pki/parse_certificate.cc b/net/cert/pki/parse_certificate.cc index 7be0777..976f421 100644 --- a/net/cert/pki/parse_certificate.cc +++ b/net/cert/pki/parse_certificate.cc
@@ -130,8 +130,9 @@ // Note that it is OK to read from the unused bits, since BitString parsing // guarantees they are all zero. for (size_t i = 0; i < bits.bytes().Length(); ++i) { - if (bits.bytes().UnsafeData()[i] != 0) + if (bits.bytes()[i] != 0) { return false; + } } return true; } @@ -262,8 +263,9 @@ // gracefully handle such certificates. if (negative) errors->AddWarning(kSerialNumberIsNegative); - if (value.Length() == 1 && value.UnsafeData()[0] == 0) + if (value.Length() == 1 && value[0] == 0) { errors->AddWarning(kSerialNumberIsZero); + } // RFC 5280 section 4.1.2.2: //
diff --git a/net/der/input.h b/net/der/input.h index 4a279a2..35d3354d 100644 --- a/net/der/input.h +++ b/net/der/input.h
@@ -56,6 +56,8 @@ // is not an option. constexpr const uint8_t* UnsafeData() const { return data_.data(); } + constexpr uint8_t operator[](size_t idx) const { return data_[idx]; } + // Returns a copy of the data represented by this object as a std::string. std::string AsString() const;
diff --git a/net/der/parse_values.cc b/net/der/parse_values.cc index 6ff5024..3eb565dd 100644 --- a/net/der/parse_values.cc +++ b/net/der/parse_values.cc
@@ -212,8 +212,7 @@ DCHECK(unused_bits == 0 || bytes.Length() != 0); // The unused bits must be zero. DCHECK(bytes.Length() == 0 || - (bytes.UnsafeData()[bytes.Length() - 1] & ((1u << unused_bits) - 1)) == - 0); + (bytes[bytes.Length() - 1] & ((1u << unused_bits) - 1)) == 0); } bool BitString::AssertsBit(size_t bit_index) const { @@ -233,7 +232,7 @@ // BIT STRING parsing already guarantees that unused bits in a byte are zero // (otherwise it wouldn't be valid DER). Therefore it isn't necessary to check // |unused_bits_| - uint8_t byte = bytes_.UnsafeData()[byte_index]; + uint8_t byte = bytes_[byte_index]; return 0 != (byte & (1 << bit_index_in_byte)); } @@ -263,7 +262,7 @@ // and the initial octet shall be zero. if (bytes.Length() == 0) return absl::nullopt; - uint8_t last_byte = bytes.UnsafeData()[bytes.Length() - 1]; + uint8_t last_byte = bytes[bytes.Length() - 1]; // From ITU-T X.690, section 11.2.1 (applies to CER and DER, but not BER): // @@ -384,12 +383,13 @@ // Convert from Latin-1 to UTF-8. size_t utf8_length = in.Length(); for (size_t i = 0; i < in.Length(); i++) { - if (in.UnsafeData()[i] > 0x7f) + if (in[i] > 0x7f) { utf8_length++; + } } out->reserve(utf8_length); for (size_t i = 0; i < in.Length(); i++) { - uint8_t u = in.UnsafeData()[i]; + uint8_t u = in[i]; if (u <= 0x7f) { out->push_back(u); } else {
diff --git a/net/der/parse_values_unittest.cc b/net/der/parse_values_unittest.cc index 77c0aa7..740ea5d 100644 --- a/net/der/parse_values_unittest.cc +++ b/net/der/parse_values_unittest.cc
@@ -351,7 +351,7 @@ EXPECT_EQ(1u, bit_string->unused_bits()); EXPECT_EQ(1u, bit_string->bytes().Length()); - EXPECT_EQ(0xFE, bit_string->bytes().UnsafeData()[0]); + EXPECT_EQ(0xFE, bit_string->bytes()[0]); EXPECT_TRUE(bit_string->AssertsBit(0)); EXPECT_TRUE(bit_string->AssertsBit(1));
diff --git a/net/der/parser_unittest.cc b/net/der/parser_unittest.cc index e1e032d0..b7de87e 100644 --- a/net/der/parser_unittest.cc +++ b/net/der/parser_unittest.cc
@@ -348,8 +348,8 @@ EXPECT_EQ(1u, bit_string->unused_bits()); ASSERT_EQ(2u, bit_string->bytes().Length()); - EXPECT_EQ(0xAA, bit_string->bytes().UnsafeData()[0]); - EXPECT_EQ(0xBE, bit_string->bytes().UnsafeData()[1]); + EXPECT_EQ(0xAA, bit_string->bytes()[0]); + EXPECT_EQ(0xBE, bit_string->bytes()[1]); } // Tries reading a BIT STRING. This should fail because the tag is not for a
diff --git a/net/tools/net_watcher/net_watcher.cc b/net/tools/net_watcher/net_watcher.cc index 05ac928..840cbda7 100644 --- a/net/tools/net_watcher/net_watcher.cc +++ b/net/tools/net_watcher/net_watcher.cc
@@ -37,7 +37,7 @@ #endif #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif namespace { @@ -147,7 +147,7 @@ int main(int argc, char* argv[]) { #if BUILDFLAG(IS_APPLE) - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif base::AtExitManager exit_manager; base::CommandLine::Init(argc, argv);
diff --git a/remoting/host/host_main.cc b/remoting/host/host_main.cc index 276c6eb3..5db7487 100644 --- a/remoting/host/host_main.cc +++ b/remoting/host/host_main.cc
@@ -29,7 +29,7 @@ #include "remoting/host/usage_stats_consent.h" #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif // BUILDFLAG(IS_APPLE) #if BUILDFLAG(IS_WIN) @@ -170,7 +170,7 @@ int HostMain(int argc, char** argv) { #if BUILDFLAG(IS_APPLE) // Needed so we don't leak objects when threads are created. - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif base::CommandLine::Init(argc, argv);
diff --git a/remoting/host/it2me/it2me_native_messaging_host_main.cc b/remoting/host/it2me/it2me_native_messaging_host_main.cc index c5f18af..e23523db 100644 --- a/remoting/host/it2me/it2me_native_messaging_host_main.cc +++ b/remoting/host/it2me/it2me_native_messaging_host_main.cc
@@ -41,7 +41,7 @@ #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) && #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "remoting/host/desktop_capturer_checker.h" #include "remoting/host/mac/permission_utils.h" #endif // BUILDFLAG(IS_APPLE) @@ -96,7 +96,7 @@ #if BUILDFLAG(IS_APPLE) // Needed so we don't leak objects when threads are created. - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif // BUILDFLAG(IS_APPLE) #if defined(REMOTING_ENABLE_BREAKPAD)
diff --git a/remoting/host/setup/me2me_native_messaging_host_main.cc b/remoting/host/setup/me2me_native_messaging_host_main.cc index c794a1b..67d5704 100644 --- a/remoting/host/setup/me2me_native_messaging_host_main.cc +++ b/remoting/host/setup/me2me_native_messaging_host_main.cc
@@ -40,7 +40,7 @@ #include "services/network/transitional_url_loader_factory_owner.h" #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif // BUILDFLAG(IS_APPLE) #if BUILDFLAG(IS_WIN) @@ -76,7 +76,7 @@ #if BUILDFLAG(IS_APPLE) // Needed so we don't leak objects when threads are created. - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif // BUILDFLAG(IS_APPLE) #if defined(USE_GLIB) && !BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/rlz/lib/rlz_lib_test.cc b/rlz/lib/rlz_lib_test.cc index 48fdf82..aad8e424 100644 --- a/rlz/lib/rlz_lib_test.cc +++ b/rlz/lib/rlz_lib_test.cc
@@ -41,7 +41,7 @@ #include "rlz/win/lib/machine_deal.h" #endif -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/threading/thread.h" #include "net/url_request/url_request_test_util.h" #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" @@ -608,7 +608,7 @@ return; #if BUILDFLAG(IS_APPLE) - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif network::TestURLLoaderFactory test_url_loader_factory; @@ -679,7 +679,7 @@ return; #if BUILDFLAG(IS_APPLE) - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif base::Thread io_thread("rlz_unittest_io_thread");
diff --git a/rlz/lib/rlz_value_store.h b/rlz/lib/rlz_value_store.h index 3f67aa84..08e67843 100644 --- a/rlz/lib/rlz_value_store.h +++ b/rlz/lib/rlz_value_store.h
@@ -20,7 +20,7 @@ #endif #if BUILDFLAG(IS_APPLE) -#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/apple/scoped_nsautorelease_pool.h" #endif namespace base { @@ -104,7 +104,7 @@ #if BUILDFLAG(IS_WIN) LibMutex lock_; #elif BUILDFLAG(IS_APPLE) - base::mac::ScopedNSAutoreleasePool autorelease_pool_; + base::apple::ScopedNSAutoreleasePool autorelease_pool_; #endif };
diff --git a/rlz/test/rlz_test_helpers.cc b/rlz/test/rlz_test_helpers.cc index eaf9a5cc..dc56eeee 100644 --- a/rlz/test/rlz_test_helpers.cc +++ b/rlz/test/rlz_test_helpers.cc
@@ -133,7 +133,7 @@ ASSERT_NO_FATAL_FAILURE( InitializeRegistryOverridesForTesting(&override_manager_)); #elif BUILDFLAG(IS_APPLE) - base::mac::ScopedNSAutoreleasePool pool; + base::apple::ScopedNSAutoreleasePool pool; #endif // BUILDFLAG(IS_WIN) #if BUILDFLAG(IS_POSIX) ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
diff --git a/services/device/compute_pressure/host_processor_info_scanner.cc b/services/device/compute_pressure/host_processor_info_scanner.cc index ed191f1..6c14523 100644 --- a/services/device/compute_pressure/host_processor_info_scanner.cc +++ b/services/device/compute_pressure/host_processor_info_scanner.cc
@@ -8,9 +8,9 @@ #include <mach/mach_host.h> #include <stdint.h> +#include "base/apple/scoped_mach_port.h" +#include "base/apple/scoped_mach_vm.h" #include "base/mac/mac_util.h" -#include "base/mac/scoped_mach_port.h" -#include "base/mac/scoped_mach_vm.h" #include "base/system/sys_info.h" #include "services/device/compute_pressure/core_times.h" @@ -36,7 +36,7 @@ } natural_t number_of_processors; - base::mac::ScopedMachSendRight host(mach_host_self()); + base::apple::ScopedMachSendRight host(mach_host_self()); mach_msg_type_number_t type; processor_cpu_load_info_data_t* cpu_infos; @@ -47,7 +47,7 @@ return false; } - base::mac::ScopedMachVM vm_owner( + base::apple::ScopedMachVM vm_owner( reinterpret_cast<vm_address_t>(cpu_infos), mach_vm_round_page(number_of_processors * sizeof(processor_cpu_load_info)));
diff --git a/testing/buildbot/filters/cr23.linux.cr23_interactive_ui_tests.filter b/testing/buildbot/filters/cr23.linux.cr23_interactive_ui_tests.filter index 981812adf..0e0b3f8 100644 --- a/testing/buildbot/filters/cr23.linux.cr23_interactive_ui_tests.filter +++ b/testing/buildbot/filters/cr23.linux.cr23_interactive_ui_tests.filter
@@ -1,11 +1,6 @@ # cr23_interactive_ui_tests that are expected to fail on linux bots: -All/ExtensionsMenuModelPresenceTest.MenuPresence/0 -AppMenuFullscreenInteractiveTest.ClosingTab --FocusRingBrowserTest.Anchor --FocusRingBrowserTest.Button --FocusRingBrowserTest.Checkbox --FocusRingBrowserTest.DarkModeButton --FocusRingBrowserTest.Radio -PermissionIndicatorsInteractiveUITest.CameraAccessAndStopTest -PermissionIndicatorsInteractiveUITest.CameraAndMicAccessAndStopTest -PermissionsFlowInteractiveUITest.CameraActivityIndicatorTest
diff --git a/testing/buildbot/internal.chrome.fyi.json b/testing/buildbot/internal.chrome.fyi.json index cebd186f..4035ad8 100644 --- a/testing/buildbot/internal.chrome.fyi.json +++ b/testing/buildbot/internal.chrome.fyi.json
@@ -21,7 +21,7 @@ ], "resultdb": { "enable": true, - "result_format": "single" + "has_native_resultdb_integration": true }, "swarming": { "dimensions": { @@ -61,7 +61,7 @@ ], "resultdb": { "enable": true, - "result_format": "single" + "has_native_resultdb_integration": true }, "swarming": { "dimensions": { @@ -179,7 +179,7 @@ ], "resultdb": { "enable": true, - "result_format": "single" + "has_native_resultdb_integration": true }, "swarming": { "dimensions": {
diff --git a/testing/buildbot/internal.chromeos.fyi.json b/testing/buildbot/internal.chromeos.fyi.json index 39ccaff..edf6149c 100644 --- a/testing/buildbot/internal.chromeos.fyi.json +++ b/testing/buildbot/internal.chromeos.fyi.json
@@ -1149,6 +1149,34 @@ "test_level_retries": 2, "timeout_sec": 21600, "variant_id": "JACUZZI_RELEASE_CHROME_FROM_TLS_LKGM" + }, + { + "autotest_name": "tast.chrome-from-gcs", + "cros_board": "jacuzzi", + "cros_img": "jacuzzi-release/R117-15564.0.0", + "experiment_percentage": 100, + "name": "chrome_criticalstaging_tast_tests JACUZZI_RELEASE_CHROME_FROM_TLS_LKGM", + "shards": 10, + "tast_expr": "(\"group:mainline\" && \"dep:chrome\" && !\"dep:no_chrome_dcheck\" && !\"dep:lacros\" && informational && \"group:criticalstaging\")", + "test": "chrome_criticalstaging_tast_tests", + "test_id_prefix": "ninja://chromeos:chrome_criticalstaging_tast_tests/", + "test_level_retries": 2, + "timeout_sec": 7200, + "variant_id": "JACUZZI_RELEASE_CHROME_FROM_TLS_LKGM" + }, + { + "autotest_name": "tast.chrome-from-gcs", + "cros_board": "jacuzzi", + "cros_img": "jacuzzi-release/R117-15564.0.0", + "experiment_percentage": 100, + "name": "chrome_disabled_tast_tests JACUZZI_RELEASE_CHROME_FROM_TLS_LKGM", + "shards": 10, + "tast_expr": "(\"group:mainline\" && \"dep:chrome\" && !\"dep:no_chrome_dcheck\")", + "test": "chrome_disabled_tast_tests", + "test_id_prefix": "ninja://chromeos:chrome_disabled_tast_tests/", + "test_level_retries": 1, + "timeout_sec": 7200, + "variant_id": "JACUZZI_RELEASE_CHROME_FROM_TLS_LKGM" } ] }, @@ -1217,6 +1245,34 @@ "test_level_retries": 2, "timeout_sec": 21600, "variant_id": "VOLTEER_RELEASE_LKGM" + }, + { + "autotest_name": "tast.chrome-from-gcs", + "cros_board": "volteer", + "cros_img": "volteer-release/R118-15574.0.0", + "experiment_percentage": 100, + "name": "chrome_criticalstaging_tast_tests VOLTEER_RELEASE_LKGM", + "shards": 10, + "tast_expr": "(\"group:mainline\" && \"dep:chrome\" && !\"dep:no_chrome_dcheck\" && !\"dep:lacros\" && informational && \"group:criticalstaging\")", + "test": "chrome_criticalstaging_tast_tests", + "test_id_prefix": "ninja://chromeos:chrome_criticalstaging_tast_tests/", + "test_level_retries": 2, + "timeout_sec": 7200, + "variant_id": "VOLTEER_RELEASE_LKGM" + }, + { + "autotest_name": "tast.chrome-from-gcs", + "cros_board": "volteer", + "cros_img": "volteer-release/R118-15574.0.0", + "experiment_percentage": 100, + "name": "chrome_disabled_tast_tests VOLTEER_RELEASE_LKGM", + "shards": 10, + "tast_expr": "(\"group:mainline\" && \"dep:chrome\" && !\"dep:no_chrome_dcheck\")", + "test": "chrome_disabled_tast_tests", + "test_id_prefix": "ninja://chromeos:chrome_disabled_tast_tests/", + "test_level_retries": 1, + "timeout_sec": 7200, + "variant_id": "VOLTEER_RELEASE_LKGM" } ] },
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl index 0ed8aa8..b98fd2edb 100644 --- a/testing/buildbot/test_suites.pyl +++ b/testing/buildbot/test_suites.pyl
@@ -137,11 +137,8 @@ ], 'mixins': [ 'skia_gold_test', + 'has_native_resultdb_integration', ], - 'resultdb': { - 'enable': True, - 'result_format': 'single' - }, }, }, @@ -392,23 +389,21 @@ 'chrome_android_finch_smoke_tests': { 'variations_android_smoke_tests': { 'isolate_name': 'variations_desktop_smoke_tests', + 'mixins': [ + 'has_native_resultdb_integration' + ], 'args': [ '--target-platform=android', ], - 'resultdb': { - 'enable': True, - 'result_format': 'single' - }, }, 'variations_webview_smoke_tests': { 'isolate_name': 'variations_desktop_smoke_tests', 'args': [ '--target-platform=webview', ], - 'resultdb': { - 'enable': True, - 'result_format': 'single' - }, + 'mixins': [ + 'has_native_resultdb_integration' + ], }, }, 'chrome_finch_smoke_tests': { @@ -7240,6 +7235,16 @@ 'CROS_JACUZZI_RELEASE_CHROME_FROM_TLS_ASH_LKGM', ] }, + 'chromeos_chrome_criticalstaging_tast_tests': { + 'variants': [ + 'CROS_JACUZZI_RELEASE_CHROME_FROM_TLS_ASH_LKGM', + ] + }, + 'chromeos_chrome_disabled_tast_tests': { + 'variants': [ + 'CROS_JACUZZI_RELEASE_CHROME_FROM_TLS_ASH_LKGM', + ] + }, }, 'chromeos_octopus_skylab_tests': { @@ -7264,6 +7269,16 @@ 'CROS_VOLTEER_RELEASE_ASH_LKGM', ] }, + 'chromeos_chrome_criticalstaging_tast_tests': { + 'variants': [ + 'CROS_VOLTEER_RELEASE_ASH_LKGM', + ] + }, + 'chromeos_chrome_disabled_tast_tests': { + 'variants': [ + 'CROS_VOLTEER_RELEASE_ASH_LKGM', + ] + }, }, 'fieldtrial_ios_simulator_tests': {
diff --git a/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc b/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc index 0698d667..17e2f8c 100644 --- a/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc +++ b/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc
@@ -9,8 +9,8 @@ #include <utility> +#include "base/apple/mach_logging.h" #include "base/containers/buffer_iterator.h" -#include "base/mac/mach_logging.h" #include "base/mac/scoped_mach_msg_destroy.h" namespace mach_fuzzer { @@ -44,7 +44,7 @@ SendablePort port; kern_return_t kr = mach_port_allocate( mach_task_self(), MACH_PORT_RIGHT_RECEIVE, - base::mac::ScopedMachReceiveRight::Receiver(port.receive_right).get()); + base::apple::ScopedMachReceiveRight::Receiver(port.receive_right).get()); MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_allocate"; port.name = port.receive_right.get();
diff --git a/testing/libfuzzer/fuzzers/mach/mach_message_converter.h b/testing/libfuzzer/fuzzers/mach/mach_message_converter.h index 0fd2b70..e144ba9 100644 --- a/testing/libfuzzer/fuzzers/mach/mach_message_converter.h +++ b/testing/libfuzzer/fuzzers/mach/mach_message_converter.h
@@ -11,7 +11,7 @@ #include <memory> #include <vector> -#include "base/mac/scoped_mach_port.h" +#include "base/apple/scoped_mach_port.h" #include "base/memory/raw_ptr.h" #include "testing/libfuzzer/fuzzers/mach/mach_message.pb.h" @@ -23,8 +23,8 @@ mach_msg_type_name_t disposition = 0; MachPortType proto_type = static_cast<MachPortType>(-1); - base::mac::ScopedMachSendRight send_right; - base::mac::ScopedMachReceiveRight receive_right; + base::apple::ScopedMachSendRight send_right; + base::apple::ScopedMachReceiveRight receive_right; }; // Holds the buffer allocation and port references for a message to be sent.
diff --git a/testing/test.gni b/testing/test.gni index 470f1c8..8d6d0a63 100644 --- a/testing/test.gni +++ b/testing/test.gni
@@ -434,7 +434,6 @@ apk_name = invoker.target_name if (defined(invoker.output_name)) { apk_name = invoker.output_name - install_script_name = "install_${invoker.output_name}" } if (defined(invoker.deps)) {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index c55593b..3fc5702 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -384,11 +384,12 @@ ], "experiments": [ { - "name": "Enabled_Active_Omnibox_Small_Margin_20221025", + "name": "Enabled_Active_Omnibox_Small_Margin_20230816", "params": { "enable_modernize_visual_update_on_tablet": "false", "modernize_visual_update_active_color_on_omnibox": "true", - "modernize_visual_update_small_bottom_margin": "true" + "modernize_visual_update_merge_clipboard_on_ntp": "true", + "modernize_visual_update_smallest_margins": "true" }, "enable_features": [ "OmniboxModernizeVisualUpdate" @@ -3112,6 +3113,21 @@ ] } ], + "ChromeOSARCVMLmkPerceptibleMinState": [ + { + "platforms": [ + "chromeos" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "ArcLmkPerceptibleMinStateUpdate" + ] + } + ] + } + ], "ChromeOSARCVMReclaimThrottle": [ { "platforms": [ @@ -3512,25 +3528,6 @@ ] } ], - "ClientSideDetectionModelOptimizationGuide": [ - { - "platforms": [ - "android", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "ClientSideDetectionModelOptimizationGuide" - ] - } - ] - } - ], "ClipboardDeemphasisAndroid": [ { "platforms": [ @@ -9035,6 +9032,22 @@ ] } ], + "LofnPermissiveUsbPassthrough": [ + { + "platforms": [ + "chromeos" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "CrOSLateBootPermissiveUsbPassthrough" + ], + "min_os_version": "15575.0.0" + } + ] + } + ], "LowerMemoryLimitForNonMainRenderers": [ { "platforms": [
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc index bc98958..5f0227d 100644 --- a/third_party/blink/common/features.cc +++ b/third_party/blink/common/features.cc
@@ -1158,7 +1158,7 @@ BASE_FEATURE(kNewBaseUrlInheritanceBehavior, "NewBaseUrlInheritanceBehavior", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kNoForcedFrameUpdatesForWebTests, "NoForcedFrameUpdatesForWebTests",
diff --git a/third_party/blink/perf_tests/layout/multicol/large-inline-formatting-context.html b/third_party/blink/perf_tests/layout/multicol/large-inline-formatting-context.html new file mode 100644 index 0000000..1204e75 --- /dev/null +++ b/third_party/blink/perf_tests/layout/multicol/large-inline-formatting-context.html
@@ -0,0 +1,32 @@ +<!DOCTYPE html> +<script src="../../resources/runner.js"></script> +<pre id="log"></pre> +<div style="columns:3; column-fill:auto; height:500px; orphans:1; widows:1;"> + <div id="target" style="display:none;"></div> +</div> +<script> + var data = "All work and no play makes Jack a dull boy. "; + // Repeat text 2^15 times. + for (var i = 0; i < 15; i++) { + data += data; + } + target.innerHTML = data; +</script> +<script> + var target = document.getElementById("target"); + var style = target.style; + + function test() { + // Avoid paint and pre-paint (we just want layout), as that's very slow with + // this test. See crbug.com/1473255 + style.display = "block"; + document.body.offsetTop; + style.display = "none"; + document.body.offsetTop; + } + + PerfTestRunner.measureRunsPerSecond({ + description: "Fragmented large inline formatting context", + run: test + }); +</script>
diff --git a/third_party/blink/renderer/DEPS b/third_party/blink/renderer/DEPS index b0f6d10..c417d6e5 100644 --- a/third_party/blink/renderer/DEPS +++ b/third_party/blink/renderer/DEPS
@@ -48,6 +48,7 @@ "+base/synchronization", "+base/sys_byteorder.h", "+base/system/sys_info.h", + "+base/task/bind_post_task.h", "+base/task/sequenced_task_runner.h", "+base/task/single_thread_task_runner.h", "+base/task/thread_pool.h",
diff --git a/third_party/blink/renderer/core/html/html_tag_names.json5 b/third_party/blink/renderer/core/html/html_tag_names.json5 index 26b9fa9..8cd65a8 100644 --- a/third_party/blink/renderer/core/html/html_tag_names.json5 +++ b/third_party/blink/renderer/core/html/html_tag_names.json5
@@ -421,6 +421,11 @@ constructorNeedsCreateElementFlags: true, }, { + name: "search", + interfaceName: "HTMLElement", + runtimeEnabled: "HTMLSearchElement", + }, + { name: "section", interfaceName: "HTMLElement", },
diff --git a/third_party/blink/renderer/core/html/parser/html_tree_builder.cc b/third_party/blink/renderer/core/html/parser/html_tree_builder.cc index fb0a3835..428d5ab 100644 --- a/third_party/blink/renderer/core/html/parser/html_tree_builder.cc +++ b/third_party/blink/renderer/core/html/parser/html_tree_builder.cc
@@ -723,6 +723,7 @@ case HTMLTag::kNav: case HTMLTag::kOl: case HTMLTag::kP: + case HTMLTag::kSearch: case HTMLTag::kSection: case HTMLTag::kSummary: case HTMLTag::kUl: @@ -2032,6 +2033,7 @@ case HTMLTag::kNav: case HTMLTag::kOl: case HTMLTag::kPre: + case HTMLTag::kSearch: case HTMLTag::kSection: case HTMLTag::kSummary: case HTMLTag::kUl:
diff --git a/third_party/blink/renderer/core/html/resources/html.css b/third_party/blink/renderer/core/html/resources/html.css index b467499b..d3c227f 100644 --- a/third_party/blink/renderer/core/html/resources/html.css +++ b/third_party/blink/renderer/core/html/resources/html.css
@@ -54,7 +54,7 @@ display: block } -article, aside, footer, header, hgroup, main, nav, section { +article, aside, footer, header, hgroup, main, nav, search, section { display: block }
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc index 6da087a..6b2bcbd 100644 --- a/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc +++ b/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc
@@ -19,7 +19,8 @@ NGFragmentItemsBuilder::NGFragmentItemsBuilder( const NGInlineNode& node, - WritingDirectionMode writing_direction) + WritingDirectionMode writing_direction, + bool is_block_fragmented) : node_(node), writing_direction_(writing_direction) { const NGInlineItemsData& items_data = node.ItemsData(false); text_content_ = items_data.text_content; @@ -32,9 +33,17 @@ // line, and each line contains 3 items; a line box, an inline box, and a // text. If it will require more than one reallocations, make an initial // reservation here. - const wtf_size_t estimated_item_count = text_content_.length() / 40 * 3; - if (UNLIKELY(estimated_item_count > items_.capacity() * 2)) - items_.ReserveInitialCapacity(estimated_item_count); + // + // Skip this if we constrained by a fragmentainer's block-size. The estimate + // will be way too high in such cases, and we're going to make this + // reservation for every fragmentainer, potentially running out of memory if + // oilpan doesn't get around to collecting it. + if (!is_block_fragmented) { + const wtf_size_t estimated_item_count = text_content_.length() / 40 * 3; + if (UNLIKELY(estimated_item_count > items_.capacity() * 2)) { + items_.ReserveInitialCapacity(estimated_item_count); + } + } } NGFragmentItemsBuilder::~NGFragmentItemsBuilder() {
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h b/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h index b126f73a74..3202cf9 100644 --- a/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h +++ b/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h
@@ -27,7 +27,8 @@ public: explicit NGFragmentItemsBuilder(WritingDirectionMode writing_direction); NGFragmentItemsBuilder(const NGInlineNode& node, - WritingDirectionMode writing_direction); + WritingDirectionMode writing_direction, + bool is_block_fragmented); ~NGFragmentItemsBuilder(); WritingDirectionMode GetWritingDirection() const {
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder_test.cc index 81f71b3..6855197 100644 --- a/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder_test.cc +++ b/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder_test.cc
@@ -47,7 +47,7 @@ // 2. |AssociateLogicalLineItems| // 3. |AddLine|. NGFragmentItemsBuilder items_builder( - inline_node, {WritingMode::kHorizontalTb, TextDirection::kLtr}); + inline_node, {WritingMode::kHorizontalTb, TextDirection::kLtr}, false); NGLogicalLineItems* line_items1 = items_builder.AcquireLogicalLineItems(); items_builder.AssociateLogicalLineItems(line_items1, *line_fragment1); items_builder.AddLine(*line_fragment1, LogicalOffset()); @@ -68,7 +68,7 @@ // container box. Then runs worklet, and add line boxes to the container // box. NGFragmentItemsBuilder items_builder( - inline_node, {WritingMode::kHorizontalTb, TextDirection::kLtr}); + inline_node, {WritingMode::kHorizontalTb, TextDirection::kLtr}, false); NGLogicalLineItems* line_items1 = items_builder.AcquireLogicalLineItems(); items_builder.AssociateLogicalLineItems(line_items1, *line_fragment1); NGLogicalLineItems* line_items2 = items_builder.AcquireLogicalLineItems(); @@ -89,7 +89,7 @@ // Custom layout can reorder line boxes. In this test, line boxes are added // to the container box in the reverse order. NGFragmentItemsBuilder items_builder( - inline_node, {WritingMode::kHorizontalTb, TextDirection::kLtr}); + inline_node, {WritingMode::kHorizontalTb, TextDirection::kLtr}, false); NGLogicalLineItems* line_items1 = items_builder.AcquireLogicalLineItems(); items_builder.AssociateLogicalLineItems(line_items1, *line_fragment1); NGLogicalLineItems* line_items2 = items_builder.AcquireLogicalLineItems(); @@ -110,7 +110,7 @@ { // Custom layout may not add all line boxes. NGFragmentItemsBuilder items_builder( - inline_node, {WritingMode::kHorizontalTb, TextDirection::kLtr}); + inline_node, {WritingMode::kHorizontalTb, TextDirection::kLtr}, false); NGLogicalLineItems* line_items1 = items_builder.AcquireLogicalLineItems(); items_builder.AssociateLogicalLineItems(line_items1, *line_fragment1); NGLogicalLineItems* line_items2 = items_builder.AcquireLogicalLineItems();
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc index c8fd9fb..d41083d 100644 --- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc +++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.cc
@@ -29,6 +29,17 @@ "Only data which can be regenerated from the node, constraints, and break " "token are allowed to be placed in this context object."); +// Return true if we're inside a fragmentainer with known block-size (i.e. not +// if we're in an initial column balancing pass, in which case the fragmentainer +// block-size would be unconstrained). This information will be used to +// determine whether it's reasonable to pre-allocate a buffer for all the +// estimated fragment items inside the node. +bool IsBlockFragmented(const NGBoxFragmentBuilder& fragment_builder) { + const NGConstraintSpace& space = fragment_builder.ConstraintSpace(); + return space.HasBlockFragmentation() && + space.HasKnownFragmentainerBlockSize(); +} + } // namespace NGInlineChildLayoutContext::NGInlineChildLayoutContext( @@ -36,7 +47,9 @@ NGBoxFragmentBuilder* container_builder, NGLineInfo* line_info) : container_builder_(container_builder), - items_builder_(node, container_builder->GetWritingDirection()), + items_builder_(node, + container_builder->GetWritingDirection(), + IsBlockFragmented(*container_builder)), line_info_(line_info) { container_builder->SetItemsBuilder(ItemsBuilder()); } @@ -46,7 +59,9 @@ NGBoxFragmentBuilder* container_builder, NGScoreLineBreakContext* score_line_break_context) : container_builder_(container_builder), - items_builder_(node, container_builder->GetWritingDirection()), + items_builder_(node, + container_builder->GetWritingDirection(), + IsBlockFragmented(*container_builder)), score_line_break_context_(score_line_break_context) { container_builder->SetItemsBuilder(ItemsBuilder()); }
diff --git a/third_party/blink/renderer/core/paint/block_flow_paint_invalidator.cc b/third_party/blink/renderer/core/paint/block_flow_paint_invalidator.cc index 05a43d4..086c3210 100644 --- a/third_party/blink/renderer/core/paint/block_flow_paint_invalidator.cc +++ b/third_party/blink/renderer/core/paint/block_flow_paint_invalidator.cc
@@ -40,17 +40,6 @@ break; } } - - if (block_flow_.MultiColumnFlowThread()) { - // Invalidate child LayoutMultiColumnSets which may need to repaint column - // rules after m_blockFlow's column rule style and/or layout changed. - for (LayoutObject* child = block_flow_.FirstChild(); child; - child = child->NextSibling()) { - if (child->IsLayoutMultiColumnSet() && - !child->ShouldDoFullPaintInvalidation()) - object_paint_invalidator.InvalidateDisplayItemClient(*child, reason); - } - } } } // namespace blink
diff --git a/third_party/blink/renderer/core/paint/box_paint_invalidator.cc b/third_party/blink/renderer/core/paint/box_paint_invalidator.cc index 153e202e..26574f56 100644 --- a/third_party/blink/renderer/core/paint/box_paint_invalidator.cc +++ b/third_party/blink/renderer/core/paint/box_paint_invalidator.cc
@@ -194,10 +194,6 @@ return PaintInvalidationReason::kLayout; } - // Needs to repaint column rules. - if (box_.IsLayoutMultiColumnSet()) - return PaintInvalidationReason::kLayout; - // Background invalidation has been done during InvalidateBackground(), so // we don't need to check background in this function.
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc index 3dae6ca..22421416 100644 --- a/third_party/blink/renderer/core/paint/paint_layer.cc +++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -1963,13 +1963,6 @@ if (!child_layer->IsSelfPaintingLayer()) continue; - // The layer created for the LayoutFlowThread is just a helper for painting - // and hit-testing, and should not contribute to the bounding box. The - // LayoutMultiColumnSets will contribute the correct size for the layout - // content of the multicol container. - if (child_layer->GetLayoutObject().IsLayoutFlowThread()) - continue; - PhysicalRect added_rect = child_layer->LocalBoundingBox(); child_layer->ExpandRectForSelfPaintingDescendants(added_rect);
diff --git a/third_party/blink/renderer/core/paint/timing/paint_timing_detector.cc b/third_party/blink/renderer/core/paint/timing/paint_timing_detector.cc index f86bc0c..b20eebd5 100644 --- a/third_party/blink/renderer/core/paint/timing/paint_timing_detector.cc +++ b/third_party/blink/renderer/core/paint/timing/paint_timing_detector.cc
@@ -458,7 +458,19 @@ if (!HasLargestTextPaintChanged(text_paint_time, text_paint_size)) { return false; } - if (lcp_details_.largest_text_paint_size_ < text_paint_size) { + // If soft nav LCP is being recorded for LCP, because lcp_details is not + // reset, if its text paint size is not smaller than the new text paint size + // corresponding to the 1st soft nav, this new text paint size would not be + // updated. The 2nd condition is to fix this. For subsequent soft + // navigations, because record_soft_navigation_lcp_for_ukm_ is true, the + // soft_navigation_lcp_details_for_ukm_ is always updated to the lcp_details_. + // the 2nd condition + // soft_navigation_lcp_details_for_ukm_.largest_text_paint_size_ < + // text_paint_size is effectively the same the 1st condition. + if (lcp_details_.largest_text_paint_size_ < text_paint_size || + (record_soft_navigation_lcp_for_ukm_ && + soft_navigation_lcp_details_for_ukm_.largest_text_paint_size_ < + text_paint_size)) { DCHECK(!text_paint_time.is_null()); lcp_details_.largest_text_paint_time_ = text_paint_time; lcp_details_.largest_text_paint_size_ = text_paint_size;
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_factory.cc b/third_party/blink/renderer/modules/indexeddb/idb_factory.cc index a5dbc12..a15d43ef 100644 --- a/third_party/blink/renderer/modules/indexeddb/idb_factory.cc +++ b/third_party/blink/renderer/modules/indexeddb/idb_factory.cc
@@ -31,6 +31,7 @@ #include <memory> #include <utility> +#include "base/task/bind_post_task.h" #include "mojo/public/cpp/bindings/pending_associated_receiver.h" #include "mojo/public/cpp/bindings/pending_associated_remote.h" #include "mojo/public/cpp/bindings/self_owned_associated_receiver.h" @@ -312,11 +313,9 @@ // callback synchronously, and thus we'd dispatch the error event // synchronously. As per IDB spec, firing the event at the request has to be // asynchronous. - context->GetTaskRunner(TaskType::kDatabaseAccess) - ->PostTask(FROM_HERE, std::move(do_open)); - } else { - AllowIndexedDB(context, std::move(do_open)); + do_open = base::BindPostTask(task_runner_, std::move(do_open)); } + AllowIndexedDB(context, std::move(do_open)); return request; } @@ -407,11 +406,9 @@ // callback synchronously, and thus we'd dispatch the error event // synchronously. As per IDB spec, firing the event at the request has to be // asynchronous. - context->GetTaskRunner(TaskType::kDatabaseAccess) - ->PostTask(FROM_HERE, std::move(do_delete)); - } else { - AllowIndexedDB(context, std::move(do_delete)); + do_delete = base::BindPostTask(task_runner_, std::move(do_delete)); } + AllowIndexedDB(context, std::move(do_delete)); return request; } @@ -469,47 +466,44 @@ base::OnceCallback<void()> callback) { DCHECK(context->IsContextThread()); SECURITY_DCHECK(context->IsWindow() || context->IsWorkerGlobalScope()); - auto wrapped_callback = - WTF::BindOnce(&IDBFactory::DidAllowIndexedDB, WrapWeakPersistent(this), - std::move(callback)); if (allowed_.has_value()) { - std::move(wrapped_callback).Run(allowed_.value()); + std::move(callback).Run(); return; } + callbacks_waiting_on_permission_decision_.push_back(std::move(callback)); WebContentSettingsClient* settings_client = nullptr; if (auto* window = DynamicTo<LocalDOMWindow>(context)) { LocalFrame* frame = window->GetFrame(); if (!frame) { - std::move(wrapped_callback).Run(false); + DidAllowIndexedDB(false); return; } - settings_client = window->GetFrame()->GetContentSettingsClient(); + settings_client = frame->GetContentSettingsClient(); } else { settings_client = To<WorkerGlobalScope>(context)->ContentSettingsClient(); } if (!settings_client) { - std::move(wrapped_callback).Run(true); + DidAllowIndexedDB(true); return; } + settings_client->AllowStorageAccess( WebContentSettingsClient::StorageType::kIndexedDB, - std::move(wrapped_callback)); + WTF::BindOnce(&IDBFactory::DidAllowIndexedDB, WrapWeakPersistent(this))); } -void IDBFactory::DidAllowIndexedDB(base::OnceCallback<void()> callback, - bool allow_access) { - if (allowed_.has_value()) { - DCHECK_EQ(allowed_.value(), allow_access); - } else { - allowed_ = allow_access; - } +void IDBFactory::DidAllowIndexedDB(bool allow_access) { + DCHECK(!allowed_.has_value()); + allowed_ = allow_access; - std::move(callback).Run(); - return; + for (auto& callback : callbacks_waiting_on_permission_decision_) { + std::move(callback).Run(); + } + callbacks_waiting_on_permission_decision_.clear(); } mojo::PendingAssociatedRemote<mojom::blink::IDBFactoryClient>
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_factory.h b/third_party/blink/renderer/modules/indexeddb/idb_factory.h index 5df41519..828e83c 100644 --- a/third_party/blink/renderer/modules/indexeddb/idb_factory.h +++ b/third_party/blink/renderer/modules/indexeddb/idb_factory.h
@@ -29,6 +29,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_IDB_FACTORY_H_ #define THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_IDB_FACTORY_H_ +#include <list> #include <memory> #include "base/task/single_thread_task_runner.h" @@ -134,10 +135,13 @@ void AllowIndexedDB(ExecutionContext* context, base::OnceCallback<void()> callback); - void DidAllowIndexedDB(base::OnceCallback<void()> callback, - bool allow_access); + void DidAllowIndexedDB(bool allow_access); + // Whether the context has permission to use IDB. absl::optional<bool> allowed_; + // Holds requests that were paused while `allowed_` is being fetched. These + // will all be invoked in order when `allowed_` is decided. + Vector<base::OnceClosure> callbacks_waiting_on_permission_decision_; mojo::PendingAssociatedRemote<mojom::blink::IDBFactoryClient> AttachRemoteClient(std::unique_ptr<IDBFactoryClient> client);
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc index abd6305..35fe42fd 100644 --- a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc +++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
@@ -1259,8 +1259,12 @@ first_output_after_configure_ = false; if (output_color_space != last_output_color_space_) { +// This should only fail when AndroidVideoEncodeAccelerator is used. Since it's +// not worth plumbing that just for this DCHECK, disable it entirely. +#if !BUILDFLAG(IS_ANDROID) DCHECK(output.key_frame) << "Encoders should generate a keyframe when " << "changing color space"; +#endif last_output_color_space_ = output_color_space; }
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index cfcc79c..ce503c9 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1978,6 +1978,10 @@ status: "experimental", }, { + name: "HTMLSearchElement", + status: "experimental", + }, + { name: "HTMLSelectListElement", status: "experimental", base_feature: "none",
diff --git a/third_party/blink/renderer/platform/weborigin/kurl.cc b/third_party/blink/renderer/platform/weborigin/kurl.cc index 54e6563..5e9bcaf4 100644 --- a/third_party/blink/renderer/platform/weborigin/kurl.cc +++ b/third_party/blink/renderer/platform/weborigin/kurl.cc
@@ -491,20 +491,19 @@ String(canon_protocol.data(), protocol_length); if (SchemeRegistry::IsSpecialScheme(Protocol())) { - base::UmaHistogramBoolean( - "URL.Scheme.SetNonSpecialSchemeOnSpecialScheme", - !SchemeRegistry::IsSpecialScheme(new_protocol_canon)); - } + bool new_protocol_is_special_scheme = + SchemeRegistry::IsSpecialScheme(new_protocol_canon); - // We don't currently perform the check from - // https://url.spec.whatwg.org/#scheme-state that special schemes are not - // converted to non-special schemes and vice-versa, but the following logic - // should only be applied to special schemes. - // TODO(ricea): Maybe disallow switching between special and non-special - // schemes, to match the standard, if we can develop confidence that it won't - // break pages. - if (SchemeRegistry::IsSpecialScheme(Protocol()) && - SchemeRegistry::IsSpecialScheme(new_protocol_canon)) { + base::UmaHistogramBoolean("URL.Scheme.SetNonSpecialSchemeOnSpecialScheme", + !new_protocol_is_special_scheme); + + // https://url.spec.whatwg.org/#scheme-state + // 2.1.1 If url’s scheme is a special scheme and buffer is not a special + // scheme, then return. + if (!new_protocol_is_special_scheme) { + return true; + } + // The protocol is lower-cased during canonicalization. const bool new_protocol_is_file = new_protocol_canon == url::kFileScheme; const bool old_protocol_is_file = ProtocolIs(url::kFileScheme);
diff --git a/third_party/blink/renderer/platform/weborigin/kurl_test.cc b/third_party/blink/renderer/platform/weborigin/kurl_test.cc index ce6b5c3..a0c2e23 100644 --- a/third_party/blink/renderer/platform/weborigin/kurl_test.cc +++ b/third_party/blink/renderer/platform/weborigin/kurl_test.cc
@@ -1058,9 +1058,10 @@ TEST(KURLTest, SetFileProtocolToNonSpecial) { KURL url("file:///path"); + EXPECT_EQ(url.GetPath(), "/path"); EXPECT_TRUE(url.SetProtocol("non-special-scheme")); - EXPECT_EQ(url.Protocol(), "non-special-scheme"); - EXPECT_EQ(url.GetPath(), "///path"); + EXPECT_EQ(url.Protocol(), "file"); + EXPECT_EQ(url.GetPath(), "/path"); } TEST(KURLTest, SetNonSpecialSchemeOnSpecialSchemeHistogram) {
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base.py b/third_party/blink/tools/blinkpy/web_tests/port/base.py index 1dbb5200..e741dd8a 100644 --- a/third_party/blink/tools/blinkpy/web_tests/port/base.py +++ b/third_party/blink/tools/blinkpy/web_tests/port/base.py
@@ -1538,14 +1538,9 @@ # Ensure that this was called at least once, to process all suites # information vts = self.virtual_test_suites() - if test in self._skip_base_tests: - return True - # We also need to check if a parent folder of a test is in the list - # Can't use "Starts with" as we need the exact entry for an efficient - # search of the set - test_folders = test.split('/') - for i in range(len(test_folders)): - if '/'.join(test_folders[:i]) + '/' in self._skip_base_tests: + + for skipped_base_test in self._skip_base_tests: + if test.startswith(skipped_base_test): return True return False @@ -2379,8 +2374,14 @@ virtual_test_suites.append(vts) if self.operating_system() in vts.platforms: for entry in vts.skip_base_tests: - self._skip_base_tests.add( - self.normalize_test_name(entry)) + normalized_base = self.normalize_test_name(entry) + # Wpt js file can expand to multiple tests. Remove the "js" + # suffix so that the startswith test can pass. This could + # be inaccurate but is computationally cheap. + if (self.is_wpt_test(normalized_base) + and normalized_base.endswith(".js")): + normalized_base = normalized_base[:-2] + self._skip_base_tests.add(normalized_base) except ValueError as error: raise ValueError('{} is not a valid JSON file: {}'.format( path_to_virtual_test_suites, error))
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py b/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py index 0b2c250..4d4e78b 100644 --- a/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py +++ b/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py
@@ -1729,6 +1729,34 @@ self.assertFalse( port.skipped_due_to_skip_base_tests('virtual/v1/b2/test2.html')) + # test.any.js shows up on the filesystem as one file but it effectively becomes two test files: + # test.any.html and test.any.worker.html. We should support skipping test.any.js. + def test_virtual_skip_base_tests_with_generated_tests(self): + port = self.make_port() + fs = port.host.filesystem + web_tests_dir = port.web_tests_dir() + fs.write_text_file( + fs.join(web_tests_dir, 'VirtualTestSuites'), '[' + '{"prefix": "v", "platforms": ["Linux"], "bases": ["external/wpt/console/test.any.js"],' + '"skip_base_tests": "ALL",' + '"args": ["-a"], "expires": "never"}' + ']') + fs.write_text_file( + fs.join(web_tests_dir, 'external/wpt/console', 'test.any.js'), '') + + self.assertTrue( + port.skipped_due_to_skip_base_tests( + 'external/wpt/console/test.any.html')) + self.assertTrue( + port.skipped_due_to_skip_base_tests( + 'external/wpt/console/test.any.worker.html')) + self.assertFalse( + port.skipped_due_to_skip_base_tests( + 'virtual/v/external/wpt/console/test.any.html')) + self.assertFalse( + port.skipped_due_to_skip_base_tests( + 'virtual/v/external/wpt/console/test.any.worker.html')) + def test_default_results_directory(self): port = self.make_port( options=optparse.Values({
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index 0fa01cf..4d67c27 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -2723,6 +2723,14 @@ crbug.com/626703 external/wpt/css/css-fonts/parsing/font-variant-invalid.html [ Crash ] # ====== New tests from wpt-importer added here ====== +crbug.com/626703 external/wpt/css/css-backgrounds/border-image-repeat-round-003.html [ Failure ] +crbug.com/626703 external/wpt/css/css-backgrounds/border-image-repeat-round-stretch-001.html [ Failure ] +crbug.com/626703 external/wpt/css/css-backgrounds/border-image-repeat-stretch-round-001.html [ Failure ] +crbug.com/626703 external/wpt/css/css-nesting/nest-containing-forgiving.html [ Failure ] +crbug.com/626703 [ Mac12 ] virtual/threaded-prefer-compositing/external/wpt/scroll-animations/view-timelines/view-timeline-range.html [ Timeout ] +crbug.com/626703 virtual/threaded/external/wpt/css/css-backgrounds/border-image-repeat-round-003.html [ Failure ] +crbug.com/626703 virtual/threaded/external/wpt/css/css-backgrounds/border-image-repeat-round-stretch-001.html [ Failure ] +crbug.com/626703 virtual/threaded/external/wpt/css/css-backgrounds/border-image-repeat-stretch-round-001.html [ Failure ] crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-grammar-error-color-001.html [ Failure ] crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-spelling-error-color-001.html [ Failure ] crbug.com/626703 external/wpt/css/css-text/line-breaking/line-breaking-atomic-010.html [ Failure ] @@ -6737,7 +6745,6 @@ crbug.com/1467798 [ Mac11-arm64 ] virtual/threaded-prefer-compositing/external/wpt/scroll-animations/css/view-timeline-range-animation.html [ Failure Timeout ] crbug.com/1364187 http/tests/misc/scroll-cross-origin-iframes.html [ Failure Pass ] crbug.com/1467800 [ Mac11 ] virtual/prefetch/external/wpt/speculation-rules/prefetch/out-of-document-rule-set.https.html?include=StatusCode404 [ Failure Pass ] -crbug.com/1467833 [ Mac12 ] virtual/threaded-prefer-compositing/external/wpt/scroll-animations/view-timelines/view-timeline-range.html [ Failure Pass ] crbug.com/1467833 [ Mac12 ] virtual/compositor-threaded-percent-based-scrolling-dsf-2/virtual/percent-based-scrolling/max-percent-delta-page-zoom.html [ Failure Pass ] crbug.com/1467833 [ Mac12 ] virtual/compositor-threaded-percent-based-scrolling/fast/scrolling/scrollbars/mouse-scrolling-on-div-scrollbar-thumb-rtl.html [ Failure Pass ] crbug.com/1467833 [ Mac12 ] virtual/compositor-threaded-percent-based-scrolling/fast/scrolling/scrollbars/mouse-scrolling-on-div-scrollbar-thumb.html [ Failure Pass ] @@ -6773,3 +6780,11 @@ crbug.com/1472631 [ Linux ] virtual/text-antialias/whitespace/normal-after-nowrap-breaking.html [ Failure Pass ] crbug.com/1472721 [ Linux ] virtual/scalefactor150/fast/hidpi/static/pointerevents/pointerevent_touch-adjustment_click_target.html [ Failure Pass ] crbug.com/1472721 [ Linux ] virtual/scalefactor200/fast/hidpi/static/pointerevents/pointerevent_touch-adjustment_click_target.html [ Failure Pass ] + +# Gardener 2023-08-16 +crbug.com/1473373 [ Mac11 ] wpt_internal/scheduler/task-signal-any-memory-abort.any.worker.html [ Pass Timeout ] +crbug.com/1473373 [ Mac12 ] wpt_internal/scheduler/task-signal-any-memory-abort.any.worker.html [ Pass Timeout ] +crbug.com/1473373 [ Mac12-arm64 ] wpt_internal/scheduler/task-signal-any-memory-abort.any.worker.html [ Pass Timeout ] +crbug.com/1473373 [ Mac13 ] wpt_internal/scheduler/task-signal-any-memory-abort.any.worker.html [ Pass Timeout ] +crbug.com/1473373 [ Mac13-arm64 ] wpt_internal/scheduler/task-signal-any-memory-abort.any.worker.html [ Pass Timeout ] +crbug.com/1473474 [ Mac13 ] wpt_internal/mediastream/mediastreamtrackprocessor-transfer-to-worker.html [ Failure Pass ]
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 b44a9a3..3b7e6cd 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
@@ -83877,6 +83877,19 @@ {} ] ], + "border-image-013.html": [ + "c7cc7d54a39e5aeb9fbf3e0eca77e94786abfbc6", + [ + null, + [ + [ + "/css/css-backgrounds/reference/border-image-013-ref.html", + "==" + ] + ], + {} + ] + ], "border-image-017.xht": [ "33492ca78b31e3078c9c63616340672aed356478", [ @@ -84036,6 +84049,19 @@ {} ] ], + "border-image-repeat-round-003.html": [ + "c2cb2372bc71acfc65946d10384a957653216162", + [ + null, + [ + [ + "/css/css-backgrounds/reference/border-image-repeat-round-003-ref.html", + "==" + ] + ], + {} + ] + ], "border-image-repeat-round-1.html": [ "c4dc17cf7df08fde88cc19114a7792dc9a7b5674", [ @@ -84094,6 +84120,19 @@ } ] ], + "border-image-repeat-round-stretch-001.html": [ + "2a23e789f905a2bd07496d805f94426ce51de682", + [ + null, + [ + [ + "/css/css-backgrounds/reference/border-image-repeat-round-stretch-001-ref.html", + "==" + ] + ], + {} + ] + ], "border-image-repeat-round.html": [ "cf87e8d16306ed34a11b7e89f7d88fe1f1212c12", [ @@ -84372,6 +84411,19 @@ {} ] ], + "border-image-repeat-stretch-round-001.html": [ + "2ac51fecf23c32eba35779bb8ba9f18d43030559", + [ + null, + [ + [ + "/css/css-backgrounds/reference/border-image-repeat-stretch-round-001-ref.html", + "==" + ] + ], + {} + ] + ], "border-image-round-and-stretch.html": [ "a9b4da706d39ab85617f8a952987d3035e0fc400", [ @@ -84508,6 +84560,19 @@ } ] ], + "border-image-slice-006.htm": [ + "45feaf60ad7ba15066e5ec4de6273f2a48c4ed0f", + [ + null, + [ + [ + "/css/css-backgrounds/reference/border-image-slice-006-ref.html", + "==" + ] + ], + {} + ] + ], "border-image-slice-007.htm": [ "e9c1827fc76c00f0bb81a23f27335a2f2074ace2", [ @@ -84682,6 +84747,19 @@ } ] ], + "border-image-width-001.htm": [ + "2a5d02e8acc53f8e6525c95a07132482e27842d7", + [ + null, + [ + [ + "/css/css-backgrounds/reference/border-image-width-001-ref.html", + "==" + ] + ], + {} + ] + ], "border-image-width-005.xht": [ "eeceedef90e9a74a20e151b346e9030fe05d84ea", [ @@ -159787,7 +159865,7 @@ ] ], "nest-containing-forgiving.html": [ - "d399142f7efb998db7ff8dffae0770c5bcea0cc2", + "561b5a3af5e73063acd58203be6dd476346137bc", [ null, [ @@ -278778,11 +278856,11 @@ "support": { ".cache": { "gitignore2.json": [ - "b9ff63f832ebd61aedf0cd2cf5aa99814a1ea949", + "f8a4e27eeb21691b10d044fa8d7ee75246097e36", [] ], "mtime.json": [ - "daf26c0923db06818e59696a38204c150b21172b", + "9fbfe94ec19461ad410558ef21b03a5de5e5c6f7", [] ] }, @@ -283195,6 +283273,10 @@ "bbfd78e37ff85dc39a82dc73cc261afd3af56a81", [] ], + "webkit-text-fill-color-property-003.html.ini": [ + "346430e7ba04e71cc003d58372c91cf751684502", + [] + ], "webkit-text-fill-color-property-004-ref.html": [ "583cf9a358dd088129b82e5708111890494948ed", [] @@ -286741,6 +286823,10 @@ "028757ca371809ae4ac88c4dd5d5b8f119529e08", [] ], + "background-026.xht.ini": [ + "bce968a71b5615586b5588f899b4e81ee72031f6", + [] + ], "background-029.xht.ini": [ "6d52bf3ec94fff65fb6a9aa4f8bd9ee6abb22841", [] @@ -286750,11 +286836,11 @@ [] ], "background-038.xht.ini": [ - "d639d0da7834b38d86d4d0c18f2d54ab8f62c9ba", + "3103bea4e7ce85221e0b0ed7205d550b5479c631", [] ], "background-041.xht.ini": [ - "7fde644bae516da5a87517843a14dbd8732dea78", + "93596c32a059992235a4de101f9c670961724982", [] ], "background-043-ref.xht": [ @@ -286825,6 +286911,14 @@ "8bdb7fa43aa67a6a7f7403c8551e949d89146aad", [] ], + "background-applies-to-005.xht.ini": [ + "0e98221519f4f483426deec3147955bbbfddfa0f", + [] + ], + "background-applies-to-006.xht.ini": [ + "ee6f4effb011796ad0d9cddbf669f740bedac3e3", + [] + ], "background-applies-to-007.xht.ini": [ "de89fe41e5809495157804c40cd7c98e31ee4764", [] @@ -286853,6 +286947,10 @@ "f72d16e0ab1a4b7764c75c5a07d9d6d9972a838a", [] ], + "background-attachment-applies-to-009.xht.ini": [ + "691d4cbe22087f0461b346c6bd849fb8a349a53c", + [] + ], "background-attachment-applies-to-012.xht.ini": [ "3690d45f221c2c8bce0b88ecf24f84e04d8fb1a1", [] @@ -287417,6 +287515,10 @@ "7a07ea21449f801796f0f5a211e7089444e328cc", [] ], + "background-image-applies-to-009.xht.ini": [ + "ae1acd853545ec92839b2b1662580729f4d11727", + [] + ], "background-image-applies-to-012.xht.ini": [ "b7ae63ca897f27934e36437b105c93f952d4d164", [] @@ -288585,6 +288687,10 @@ "ea9e7c3bcdc57c08b92cb58c19870f26b0098882", [] ], + "border-bottom-color-013.xht.ini": [ + "8fc0e7cab7be0a63b6ae2428ba7e913a3120ecdc", + [] + ], "border-bottom-color-014-ref.xht": [ "d3082895e93514d855cf9159e01d28eb650b3ae6", [] @@ -289133,6 +289239,10 @@ "2615ee151e43987c95849c59f303b0ee18a3db58", [] ], + "border-left-color-036.xht.ini": [ + "2ad3f7b5eb8a6e5292222f1b6e3b8e75cab6980b", + [] + ], "border-left-color-039.xht.ini": [ "d514cbec1eb40862a7477e3655004d32c631f384", [] @@ -289513,6 +289623,10 @@ "6f5add32ab5935f0bbdcb1904c0f114a4e418d41", [] ], + "border-right-color-058.xht.ini": [ + "3796117b92677acc2c724931dc2ed9b35b3706ca", + [] + ], "border-right-color-059.xht.ini": [ "d8ce56eaf6c6b97795747138bfc898f8657f74e5", [] @@ -289541,6 +289655,10 @@ "c1f99b5c0b0332c589eb6cc8ed7e04ccb8e10067", [] ], + "border-right-color-072.xht.ini": [ + "375b145c9818a8a0677d02be77f6566bae557f2d", + [] + ], "border-right-color-073-ref.xht": [ "a851dd306fd2b1925d59c41f1d240e8e48c02a02", [] @@ -289601,6 +289719,10 @@ "94d16c8f06901ecf3d84d6b6ac16c55b2c71627b", [] ], + "border-right-color-109.xht.ini": [ + "9f83c35bc7b5309c029b5428161e1f1324af5a17", + [] + ], "border-right-color-110-ref.xht": [ "7c5049a194bae7f75457de0ea7b61b9a7ed5934b", [] @@ -289649,6 +289771,10 @@ "9bce8d0dcbd7dfee7ffd27cc3f316087ef4a2fe3", [] ], + "border-right-color-130.xht.ini": [ + "13e6b33d0d513fa709100b3f900895838038f0f9", + [] + ], "border-right-color-131-ref.xht": [ "28f861bbf670d6afd46d82cfa1301a1b63050dda", [] @@ -289793,6 +289919,10 @@ "8a8790a769b2702323d2e6a7aad4ce7ef04baac3", [] ], + "border-right-width-applies-to-013.xht.ini": [ + "df3990dc95bfc92fe9054a143a2937b4e3fbaf22", + [] + ], "border-right-width-applies-to-015.xht.ini": [ "69881c3f27d3b452b0869ccda4bdd6786d1c349f", [] @@ -289865,6 +289995,10 @@ "10a335b145c9f28835984578bd458b2a12ab3593", [] ], + "border-top-color-041.xht.ini": [ + "224272a53f71bea663cd99618e9df212b576b0e5", + [] + ], "border-top-color-044.xht.ini": [ "f1426766c8d2c9cf3312b2ba0fc920a3f211e37e", [] @@ -289993,6 +290127,10 @@ "ff16677883ac2733e1874e65d532bc22243b5e51", [] ], + "border-top-width-084.xht.ini": [ + "cda67986af9ff9e4dfaa9da2d0ad1539ebcf3290", + [] + ], "border-top-width-095-ref.xht": [ "28004ecf439e612235665832b91b1763787c31a1", [] @@ -290009,6 +290147,10 @@ "a319bd2da7d91ff43ad3617f11a3f0eb1062116a", [] ], + "border-top-width-applies-to-005.xht.ini": [ + "50cca6fca603e06a505f5c7fa2d3dadfd36d5147", + [] + ], "border-top-width-applies-to-007.xht.ini": [ "073d9ff5a8dc934a1b8a20ab34341b25908cc22d", [] @@ -290017,6 +290159,10 @@ "814a787c5b5c4b9838ee6e8fa781c51c4791e77c", [] ], + "border-top-width-applies-to-012.xht.ini": [ + "c683b9fc5e0478f9fca81ac3a9bf30313c15498f", + [] + ], "border-top-width-applies-to-013.xht.ini": [ "9b6d41cab1ff033453f97aca293c21f218a27364", [] @@ -292377,6 +292523,10 @@ "ddfa2d25853da529e4d6130fa64b140620f41d51", [] ], + "line-height-applies-to-003.xht.ini": [ + "ac3165adcfd159bb005cabf9cb4b21d124d4c39a", + [] + ], "line-height-applies-to-005-ref.xht": [ "c648cbeaf7379b9255053fcd1838e7bdaa554803", [] @@ -292609,6 +292759,10 @@ ] }, "normal-flow": { + "block-formatting-context-height-001.xht.ini": [ + "a6b78fa79d0450496107216f8c08d7d969f59378", + [] + ], "block-formatting-context-height-002.xht.ini": [ "01aa1c65eaec0a993e5ee722e867c66c249135ce", [] @@ -293083,6 +293237,10 @@ "42846646baf81de0c3c30087833510c49f6ab0d6", [] ], + "height-applies-to-015.xht.ini": [ + "1fd3a367ba56f815367d8746bf6e0a3ebaa7d1e5", + [] + ], "height-inherit-001.xht.ini": [ "580883b3efe51aaadbe5508b7f904cf3716b59b3", [] @@ -293399,6 +293557,10 @@ "c9e493917e13205815e41b692ab666dea71e6aca", [] ], + "max-height-067.xht.ini": [ + "0aa0fe0d2d436224e8cd464aad107d000985262c", + [] + ], "max-height-069-ref.xht": [ "102b5f913dad52285e05a33ed3bbc797c0880f66", [] @@ -293495,6 +293657,10 @@ "b6301113707db228ce267640905f9bcb81637fc8", [] ], + "max-width-103.xht.ini": [ + "b3215c9fda6baeb3050877a9a42ee4513cb32ae7", + [] + ], "max-width-105-ref.xht": [ "4d525a73575cfc3cc180085f3d340359d818bafd", [] @@ -293967,6 +294133,10 @@ "22fe6a2342ca16e50ebb1ebb33fa68d7feac551a", [] ], + "absolute-non-replaced-height-005.xht.ini": [ + "8662e2bce2fc2e2964afeb0bc9cb2d5b3b8bd34f", + [] + ], "absolute-non-replaced-height-006-ref.xht": [ "0253cc9345e4e923fccb5a947e5851b61026a2ab", [] @@ -293995,6 +294165,10 @@ "51ec44e9cf55daa92b0822590933275a6706b9aa", [] ], + "absolute-non-replaced-height-009.xht.ini": [ + "7204b6655d3729d506cc9996d739f50e8b440b3f", + [] + ], "absolute-non-replaced-height-010.xht.ini": [ "261c6049a6dd40a7d8a6b41c5005dc4fcb08685e", [] @@ -294068,7 +294242,7 @@ [] ], "absolute-non-replaced-width-001.xht.ini": [ - "7078ebe9f4a923dd1737c9c5ace25a869d52f7b9", + "8aba81b03a8c3fb9652a57ad9ef333c2e3e0824f", [] ], "absolute-non-replaced-width-002-ref.xht": [ @@ -294252,7 +294426,7 @@ [] ], "absolute-replaced-height-030.xht.ini": [ - "b2702e2e6e1321088b700ff59cd6d8fe2083e036", + "7ff4d4b552a5a5f74c79cd0d0cd22f2d3fa0b0a9", [] ], "absolute-replaced-height-032.xht.ini": [ @@ -294867,6 +295041,10 @@ "1e15a35a60f8895691b09f4205c0fc9a83e6defb", [] ], + "left-applies-to-007.xht.ini": [ + "200d2a5c294c31821ad40701178252704aee6700", + [] + ], "left-offset-001-ref.xht": [ "47a60127eb8a2e9600606fa140a8c174f27aa68c", [] @@ -295989,6 +296167,10 @@ "4ffe0b0ea728d044ad1a17b5050d84166fcdd7db", [] ], + "border-collapse-dynamic-column-002.xht.ini": [ + "0ab87683e315e68f1295ac3761f636ab7067b490", + [] + ], "border-collapse-dynamic-column-003.xht.ini": [ "b34402ba390ee6a88cea9beb84a696a538918414", [] @@ -296349,6 +296531,10 @@ "2db121294f4ff409acf0b275b72e728f6024fd7e", [] ], + "fixed-table-layout-018.xht.ini": [ + "ed7a1cd4faef8f7a82bb7ba8391902e3d75b7fdb", + [] + ], "fixed-table-layout-020.xht.ini": [ "a29cc2e0ab9eb335bbf37d5e6fb2c1645a336078", [] @@ -296373,6 +296559,10 @@ "ef00a145e984e174e26a8d07405c9564dda16f06", [] ], + "fixed-table-layout-030.xht.ini": [ + "2b291cc77950551b05505789ff9b57936b758285", + [] + ], "height-table-cell-001.xht.ini": [ "d84b2ddac2ba827e26e42822502e3db67feafc36", [] @@ -297025,6 +297215,10 @@ "8c36cafb4690618dd4b9d9ee4aebb3d8e67ed7f0", [] ], + "table-visual-layout-017.xht.ini": [ + "5170a6f7b4354b060ce98c68b0de76f4479d76b3", + [] + ], "table-visual-layout-026d.xht.ini": [ "ef149f167782152d865c6560442913b9f2a1ebc7", [] @@ -298005,6 +298199,10 @@ "b5978c747cdc9c39b7034318ff217079c6b5cf9c", [] ], + "white-space-processing-017.xht.ini": [ + "e2dada116cabe0afe60fc78ee77345c1dca4ccd6", + [] + ], "white-space-processing-024.xht.ini": [ "e29b699785ba8ee07fa4b392122534a9346e9042", [] @@ -299579,6 +299777,10 @@ "e3cc0375a491a228af8ccd102269716063f4829a", [] ], + "attachment-local-clipping-color-1.html.ini": [ + "ea0a7bcedfc15100fbe81c9d4196a734aa7f4e17", + [] + ], "attachment-local-clipping-color-2.html.ini": [ "ac8ff1137a870217ffeca9bac12a6eeebea4aeba", [] @@ -300410,6 +300612,10 @@ "166f9e987bf5b3e64df2a0f1c87c29dfdfe97af3", [] ], + "background-size-with-negative-value.html.ini": [ + "6425340ca057fb871e69fe0e8f8de22c16a1a75a", + [] + ], "bg-color-with-gradient-ref.html": [ "5c76f3408aebe6e2964215af3f98745e833e190e", [] @@ -300494,6 +300700,10 @@ "942f3eb8c5697b95f93b562056c4359efcea235b", [] ], + "border-image-repeat-round-003.html.ini": [ + "d00f5a59463d9b8d0df465ed2e4d557a81c68f2d", + [] + ], "border-image-repeat-round-1-ref.html": [ "298b46dd1284cebf66356b25e4a347e7108fe24e", [] @@ -300506,6 +300716,10 @@ "e76f7cee8321ac9d02107e0d108e4a54cb168da8", [] ], + "border-image-repeat-round-stretch-001.html.ini": [ + "37e4faeb3e332a0ca46119437815b7fc49fdfae6", + [] + ], "border-image-repeat-round.html.ini": [ "eaae90cb1c1b11c36a72015932da16b3f4386e55", [] @@ -300558,6 +300772,10 @@ "828f7ca70e53bb0b95de61bea9b8bb82b3da45a7", [] ], + "border-image-repeat-stretch-round-001.html.ini": [ + "08a3c2693aa4a90dde2bfbeacd24c2f20b0f8e65", + [] + ], "border-image-round-and-stretch.html.ini": [ "df7c5eebd2a2bbc01bcce7a22fbc6a20209f9d4c", [] @@ -300796,6 +301014,10 @@ "4bb4cbd4178090ef89be1133a069e22d21fd5fbc", [] ], + "box-shadow-outset-without-border-radius-001.html.ini": [ + "c8a3184730311cf839599daace06256d35d347b3", + [] + ], "box-shadow-overlapping-001.html.ini": [ "7c9c777c5601810ae8b24b0918e394758b9f6198", [] @@ -301209,6 +301431,10 @@ "c11db014019ff4616f7a1c259773194d71630f74", [] ], + "border-image-013-ref.html": [ + "789afe1a7a2f784244dec64796b3f4ee04121390", + [] + ], "border-image-image-type-003-ref.html": [ "690f9ad11ad0e63d57c8c7ee10737690b7c8a061", [] @@ -301221,14 +301447,26 @@ "0086544c0f75ac9466264dba8fd40aba6edc959b", [] ], + "border-image-repeat-round-003-ref.html": [ + "4b63a4c032e02f08fb9d436e536fe9839dd24c6c", + [] + ], "border-image-repeat-round-ref.html": [ "327013a93369af6cf023fef0668a4c5060e04f24", [] ], + "border-image-repeat-round-stretch-001-ref.html": [ + "32253750f0b845fbb8218dc286232674a81b6590", + [] + ], "border-image-repeat-space-011-ref.html": [ "226140d15235f68b6966fe3ed832e3d36337fd0b", [] ], + "border-image-repeat-stretch-round-001-ref.html": [ + "931dd3be5e870a85cdf16823b7a723cf7c1eb06f", + [] + ], "border-image-round-and-stretch-ref.html": [ "4f0ffdbc526a4c3f899cc72277f9d46688538c6b", [] @@ -301249,6 +301487,10 @@ "d4a4040a7854d7578e94b372650318792925f662", [] ], + "border-image-slice-006-ref.html": [ + "d4312675731e10f5f461d5a1c37891e6c1005455", + [] + ], "border-image-slice-007-ref.html": [ "2581d8dabfc7927c6ce8c1a5bedd9ccb2af82e52", [] @@ -301265,6 +301507,10 @@ "22c5f4dfb8b40fddeb3eeb0af52056dfebf623e1", [] ], + "border-image-width-001-ref.html": [ + "a3e1a91eef02acf577a36f23aeef0a7f9d687c9f", + [] + ], "border-image-width-009-ref.html": [ "cc922984355c263bcf2caf8a45520c478790c99b", [] @@ -301553,6 +301799,10 @@ "c372de1dbd264ede31344915236b9420147586e4", [] ], + "4bicolor-squares.png": [ + "252710dc44d8b3965d3109840f29550f0e46fbca", + [] + ], "500x500-red-with-green-center.png": [ "263102bf8aade13ccfb5e7b1c8f375e3f7a61a7b", [] @@ -301689,6 +301939,10 @@ "97a94592dd597b75d164592f54d5db2d2023011c", [] ], + "green-dot.png": [ + "034353e234e50a2fc858736e0fa619ca17e4ec53", + [] + ], "green.png": [ "5c5217b1e7b27ef88f06cf3ced87f5e115eadbe6", [] @@ -304656,6 +304910,14 @@ "b205195cce6f076800044aa365b58481ba7bd6fa", [] ], + "hsl-007.html.ini": [ + "e57393b5d0fb6343024e8e012ea5529345f5ddf2", + [] + ], + "hsla-001.html.ini": [ + "d8ec9bd8a0b6960c1e6908c51581660cfb83ae83", + [] + ], "hsla-004.html.ini": [ "930046262fd1f50fa00a483954b3abf3bc8e9c5e", [] @@ -306165,7 +306427,7 @@ [] ], "contain-size-select-elem-003.html.ini": [ - "814624151c200deb97adb37e12713837cc110383", + "366687baa845d73400adae40e0ad163809470e11", [] ], "contain-size-select-elem-004-ref.html": [ @@ -306202,7 +306464,7 @@ [] ], "canvas-as-container-003.html.ini": [ - "66bfbf0edc2870bc7e7ff349806d0c195e8602dc", + "52ae4b5a8681ef6e0cadf75ce030f544230c9b91", [] ], "canvas-as-container-004.html.ini": [ @@ -308927,7 +309189,7 @@ [] ], "display-contents-dynamic-generated-content-fieldset-001.html.ini": [ - "a7c697a6f2578f98bcf2c199c4dd75154ee1bbfb", + "e4253efdb6720c06ed5b6467685bdce38904715c", [] ], "display-contents-dynamic-list-001-none.html.ini": [ @@ -309366,6 +309628,10 @@ [] ] }, + "align-content-001.htm.ini": [ + "57815c6247006cd202bca0b2dc591b29fc51d517", + [] + ], "align-content-004.htm.ini": [ "eedc4df5615001a5a0d2295c732e614e9b5bbc79", [] @@ -309390,6 +309656,10 @@ "91cfd6ddee0f9f6b4b48d8f3b312413875afbe67", [] ], + "align-items-002.htm.ini": [ + "af70703d8a9ff055d1bdb9ec37fbc0cfa2f008b2", + [] + ], "align-items-003.htm.ini": [ "931f0146b47fdb6cbfa8e78ad9b116eb3373c389", [] @@ -309517,7 +309787,7 @@ [] ], "css-flexbox-row.html.ini": [ - "1383811078306f2e915bed975f4081eb6a12d060", + "8d6de9d208c0c9240fb835931c359eb121921e0c", [] ], "css-flexbox-test1-ref.html": [ @@ -309716,6 +309986,10 @@ "3a5ec04752fa0f2b6a61ddecdd8ce67f38f0fd0d", [] ], + "flex-flow-004.html.ini": [ + "ca6c415020b53ed36fb799538675506c03e7e35c", + [] + ], "flex-flow-005.html.ini": [ "c77bfca88bef2cbc1a808290453ab753be459574", [] @@ -310546,6 +310820,10 @@ "f9ae88ce4849283208f144c5a7cf0702ea2633de", [] ], + "flexbox-overflow-horiz-004.html.ini": [ + "529a1a5b52c41fc33efce0442ef201cba531e33b", + [] + ], "flexbox-overflow-horiz-005-ref.html": [ "589712c6ac7985978b47144f3fe3d2772749335b", [] @@ -310658,6 +310936,10 @@ "48d17019b6dee0a44511f06582c839ab69479236", [] ], + "flexbox-with-pseudo-elements-002.html.ini": [ + "7fd990db7228d22e03f7b77b8bcb88ea8bf58d24", + [] + ], "flexbox-with-pseudo-elements-003-ref.html": [ "bdb973023aed9fc02176521062c9eff7ec94bc68", [] @@ -311343,7 +311625,7 @@ [] ], "flexbox_flex-initial-2.html.ini": [ - "7ca12820586bcf75925c7b4c2952aac50f8c9477", + "b43c30e5ec292117b7392d1692597c79782142b6", [] ], "flexbox_flex-initial-ref.html": [ @@ -312812,6 +313094,10 @@ "5b2dcaae93d883f5fbe4403e1c75d24f0fce0926", [] ], + "table-with-float-paint.html.ini": [ + "61fc73bd4947414c398d0084fe05c225e453e145", + [] + ], "webkit-box-vertical-writing-mode-ref.html": [ "81da85dc464f0f79570e3b8b765256e44dcca589", [] @@ -313196,6 +313482,10 @@ "acde1ea6f44c5db0a2b74cb8160b0ab1df973d2a", [] ], + "font-family-name-022.xht.ini": [ + "70f063c0cb67445583230b415be93d1a3a3f7ac3", + [] + ], "font-family-name-023-ref.xht": [ "78e1512de81e2e2a7069f6f721daccaf01d4fb23", [] @@ -313413,7 +313703,7 @@ [] ], "font-palette-33.html.ini": [ - "cc813c20182486a8b74b918cbc91349c44860a18", + "39c5b22d9e6f4d482e972d7c5a030fcb4d4e2e85", [] ], "font-palette-34-ref.html": [ @@ -322110,7 +322400,7 @@ [] ], "grid-item-aspect-ratio-stretch-3.html.ini": [ - "0d5234e06eaacb55312fb335e30d5a4c76f9308d", + "01ae1c1da997cce3d7bfa5038a613efd95180fb5", [] ], "grid-item-aspect-ratio-stretch-4-ref.html": [ @@ -324368,6 +324658,10 @@ "9189fb5f7e2f85fa7b9fef04c6b764c35bb96515", [] ], + "table-grid-item-dynamic-004.html.ini": [ + "c0ee0548c22035b59cca01ba57aa7a75920d5945", + [] + ], "test-plan": { "index.html": [ "039f3a87c1a7d042af4ac043b1a5c4c4cb6c0d1e", @@ -326279,26 +326573,18 @@ ] }, "baseline-source": { - "baseline-source-first-textarea-001.tentative-expected.txt": [ - "d6abbcc731372d653a8457018030b20e5c92d3f9", - [] - ], "baseline-source-first-textarea-001.tentative.html.ini": [ "ffd115caf0199cf010a1ac886a5fa511a609ac74", [] ], "baseline-source-first-textarea-002.tentative-expected.txt": [ - "7fa1abf50a0c187155e03f9eb9b843b9c51c8567", + "1d3ebd14bff9a0e9587fca3fd4dde867f7786461", [] ], "baseline-source-first-textarea-002.tentative.html.ini": [ "acd2f0c6a1c9d33107c1d605267cac73567c40ef", [] ], - "baseline-source-first-textarea-003.tentative-expected.txt": [ - "677e0c641c5dd8ccc390ab98127cd38c159dacf1", - [] - ], "baseline-source-first-textarea-003.tentative.html.ini": [ "a23f7d29d6cb68fefba81e2863d357c47727bfb7", [] @@ -326312,7 +326598,7 @@ [] ], "baseline-source-last-textarea-001.tentative-expected.txt": [ - "aa33cde0bbe9eccd0549afe9831a542d1fa592c2", + "0056956f92c54559d96a270c4a56dd4551aead43", [] ], "baseline-source-last-textarea-001.tentative.html.ini": [ @@ -326320,7 +326606,7 @@ [] ], "baseline-source-last-textarea-002.tentative-expected.txt": [ - "051e34ec11dac7cec4f8b5dcae147ae3c4a50028", + "31b3070d4669770f6e6910e39284dbe08cf12d5b", [] ], "baseline-source-last-textarea-002.tentative.html.ini": [ @@ -326328,7 +326614,7 @@ [] ], "baseline-source-last-textarea-003.tentative-expected.txt": [ - "08a9dd8d17fe43d42f8b61d893f8be2cd002021c", + "4eaac8dbbf73ab91ef17b6697cad931d75068cdf", [] ], "baseline-source-last-textarea-003.tentative.html.ini": [ @@ -327769,7 +328055,7 @@ [] ], "logical-values-float-clear-2.html.ini": [ - "303554e563ff59d4c24e1522c21faabffc1b609b", + "302d9db7b5735daba9aa7f0211a2cb5ac52067c4", [] ], "logical-values-float-clear-3.html.ini": [ @@ -330554,6 +330840,10 @@ "a559b92a8a516aeb34fe5abc807e5cd6f8a32277", [] ], + "multicol-width-ch-001.xht.ini": [ + "0cdf2faf61c3c10d49f26ba77e61fc12959423f4", + [] + ], "multicol-width-ch-ref.xht": [ "75002b419d206f6d5d5bc710d8640e3baf1bfc58", [] @@ -331067,7 +331357,11 @@ [] ], "nest-containing-forgiving-ref.html": [ - "36b07c92b6fdcbb9bca66a4292bd871af7e7d696", + "8fb7c8674a534aa6e68501916616304064696145", + [] + ], + "nest-containing-forgiving.html.ini": [ + "cbc29917d05d9d04bf54caddbef13d90b3200383", [] ], "nesting-basic-ref.html": [ @@ -331345,7 +331639,7 @@ [] ], "overflow-clip-margin-visual-box.html.ini": [ - "da6cc9b927fd6d090e8430f043c3c13bde58b96e", + "9b512744f2db2cff8e5a6650e10fbc320c8357a2", [] ], "overflow-clip-rounded-table-ref.html": [ @@ -332718,7 +333012,7 @@ [] ], "overlay-transition-backdrop.html.ini": [ - "9a102a9f26171f128af1e76cf2f0b54d1c957e3e", + "5cbd39c416d2d0109b03dc4709b37b9a01524a38", [] ], "overlay-transition-dialog-ref.html": [ @@ -332982,7 +333276,7 @@ [] ], "vrl-rtl-ltr.tentative.html.ini": [ - "1c4e696f65df6a676ce0ea86bc43f2be3f0b55fe", + "28f89c9cc45d919485c616a01ac098e523a6f927", [] ], "vrl-rtl-rtl.html.ini": [ @@ -336131,7 +336425,7 @@ [] ], "shape-outside-box-009.html.ini": [ - "481a42bfeca8c3130c93f6e0565997bc163c1663", + "cc4422095dce6f07d755b7b7096c86afd61c8245", [] ] }, @@ -337495,6 +337789,10 @@ "4784f326ecf29cb565bd8f36cc8c83768e5fa9bd", [] ], + "replaced-element-012.html.ini": [ + "ae2e9e534a5e7afbeb5b0cdcd68fc9baf7f83b8a", + [] + ], "replaced-element-013.html.ini": [ "5adf5047774996ad2db7f3b9774bdda4f710183a", [] @@ -337634,6 +337932,10 @@ "c468c5b5b9914b149d159bff57521b614c68d739", [] ], + "box-sizing-border-box-003.xht.ini": [ + "1f161f55239cbbfc8c9caf1b6313a20c558a947b", + [] + ], "box-sizing-border-box-004-ref.xht": [ "a635acd13e493809080c88c013965ab04eb05207", [] @@ -339727,6 +340029,10 @@ "e5878b44b4be014fd00e8dcf4bae85ef5a1eb957", [] ], + "css3-text-line-break-opclns-031.html.ini": [ + "e14defe6948c239b5c555841d06fea6f6129741b", + [] + ], "css3-text-line-break-opclns-033.html.ini": [ "9399d7cf340ab81a4138ab5fa7d5ae670989d638", [] @@ -344551,6 +344857,10 @@ "e024cd6c71bd70e042d85a2d12d3775d44009193", [] ], + "white-space-pre-032.html.ini": [ + "64941fce3b1e1afb06abe789446d1157f05cb78f", + [] + ], "white-space-pre-035.html.ini": [ "3270510b31550b2e31a8d36c6e4f60565c32d51e", [] @@ -345053,6 +345363,10 @@ "ef0bf49032d0606d770fa37e13f407f9e6692b0f", [] ], + "word-break-break-all-014.html.ini": [ + "115cfea97075cc0ca1b7902f01aada639200e56b", + [] + ], "word-break-break-all-021.html.ini": [ "6dcd3d9d10d137b94b0b937247438c8563a29d99", [] @@ -347149,6 +347463,10 @@ "a1fca20c7c24907bc047165458cbf55a046ab310", [] ], + "svg-matrix-065.html.ini": [ + "28db25d101c81afdbf198b5f0be59d6fbee4ff70", + [] + ], "svg-matrix-067.html.ini": [ "42951e8902f4149691330b3513b1c7d383c6d4e4", [] @@ -347661,6 +347979,10 @@ [] ] }, + "svg-skewx-001.html.ini": [ + "cfeb825b2cf941dfb607c4d7bd4e6c790014b434", + [] + ], "svg-skewx-006.html.ini": [ "d6d04dea8a0b1dc813ead41b9feea273452e3756", [] @@ -350376,8 +350698,12 @@ "8d4e03f33fa64c9e6b45287c8ae02ea5858a9985", [] ], + "box-sizing-005.html.ini": [ + "bae24324bbdd17e6eeae0240d0a6fb8510b38ba0", + [] + ], "box-sizing-007.html.ini": [ - "5cf40fddffb01b24bf25f51e366991657bb0d311", + "94642432e90737914f516104f36eb80a6181f77e", [] ], "box-sizing-024.html.ini": [ @@ -350478,7 +350804,7 @@ [] ], "kind-of-widget-fallback-button-background-size-001.html.ini": [ - "b09697005d12c3d6a8ea919fa7491bc08156a293", + "47cade4b95525818e08347869f884fc0ca536b7a", [] ], "kind-of-widget-fallback-button-border-block-end-color-001.html.ini": [ @@ -350498,23 +350824,27 @@ [] ], "kind-of-widget-fallback-button-border-bottom-left-radius-001.html.ini": [ - "9b4e9fb28f7b0857985e6a0dceefc52226c3c017", + "84b2ee44b1aa646186280e1dc4f0b26ed36daecc", [] ], "kind-of-widget-fallback-button-border-bottom-right-radius-001.html.ini": [ "758c3d0336a6e84449431603fda982e1df51c153", [] ], + "kind-of-widget-fallback-button-border-bottom-style-001.html.ini": [ + "fc4344f316dada77b471bdd3407311cc32305da8", + [] + ], "kind-of-widget-fallback-button-border-bottom-width-001.html.ini": [ "2036b80044361c3e6df835fd9d511bfcc6ca7138", [] ], "kind-of-widget-fallback-button-border-end-start-radius-001.html.ini": [ - "f720f41f53666288004f07ce7df489d5dc4710ee", + "fc7094f33e7b1b499c41161ede1e2ceae112752a", [] ], "kind-of-widget-fallback-button-border-image-outset-001.html.ini": [ - "ee4df6d0bae8ea5a832a022f567265fbcef0fad6", + "c9fa79f127d2134337acd911921c4317f0d78ca4", [] ], "kind-of-widget-fallback-button-border-image-slice-001.html.ini": [ @@ -350530,13 +350860,17 @@ [] ], "kind-of-widget-fallback-button-border-inline-end-width-001.html.ini": [ - "191d637eead178d3d675e988722ce6eea3d73654", + "f4f737dca5f307c61b9697c61e42c060d37863c8", [] ], "kind-of-widget-fallback-button-border-inline-start-style-001.html.ini": [ "6ae6faa3ee63ebff0b82ef4d6cbd1b4c86cec359", [] ], + "kind-of-widget-fallback-button-border-left-style-001.html.ini": [ + "0bbe6c6387ad88e83f7da0c140300fc846a467bc", + [] + ], "kind-of-widget-fallback-button-border-right-style-001.html.ini": [ "1888981562332dcff34d4c761ee164e93f5d3033", [] @@ -350549,6 +350883,10 @@ "ea5128bc85b90815807b9c224a8d4a6208ed555d", [] ], + "kind-of-widget-fallback-button-border-start-start-radius-001.html.ini": [ + "1e7bb363b24774450cee47efc1c12b7861dbed4e", + [] + ], "kind-of-widget-fallback-button-border-top-right-radius-001.html.ini": [ "3d98057dcafe0077a419ce29318e9b6f6b37e69d", [] @@ -350798,7 +351136,7 @@ [] ], "kind-of-widget-fallback-input-button-border-image-width-001.html.ini": [ - "bdd8aef7248de788980823ad241227225c7845f7", + "263d9b6956b47e5a56ffd9433420794367f33775", [] ], "kind-of-widget-fallback-input-button-border-inline-end-color-001.html.ini": [ @@ -350830,7 +351168,7 @@ [] ], "kind-of-widget-fallback-input-button-border-start-end-radius-001.html.ini": [ - "0d50a5dcf3c721d64b86d56bd46ebcdc101267ce", + "85cdb77f8a5d67309023f666b2d9450645c1bec5", [] ], "kind-of-widget-fallback-input-button-border-start-start-radius-001.html.ini": [ @@ -350882,7 +351220,7 @@ [] ], "kind-of-widget-fallback-input-reset-border-block-end-style-001.html.ini": [ - "426e20019b9b24429125341a7da210882bff1441", + "c454e2c25e7075dac89672f0daf973d2218b2115", [] ], "kind-of-widget-fallback-input-reset-border-block-end-width-001.html.ini": [ @@ -351022,7 +351360,7 @@ [] ], "kind-of-widget-fallback-input-search-border-block-end-style-001.html.ini": [ - "2faf17980b15aa7d2a7f7d44227684abda6b1a6b", + "36eca302994d50ddf20fcfcdefc2b4b0d5336a9f", [] ], "kind-of-widget-fallback-input-search-border-block-end-width-001.html.ini": [ @@ -351038,11 +351376,11 @@ [] ], "kind-of-widget-fallback-input-search-border-block-start-width-001.html.ini": [ - "e5fdc27936d27e0660cd94c034024d09c8cdb42d", + "c23ebe75df6e9bed05949c56f6c1e546c93aca70", [] ], "kind-of-widget-fallback-input-search-border-bottom-color-001.html.ini": [ - "52c152f6dcffca0af3767157a260ecc39a0c2a0b", + "ccc02f05b20b43a76d07d2791f61a365d4f1d53d", [] ], "kind-of-widget-fallback-input-search-border-bottom-right-radius-001.html.ini": [ @@ -351078,7 +351416,7 @@ [] ], "kind-of-widget-fallback-input-search-border-image-width-001.html.ini": [ - "0a07ac8f157e2209143222eb340e330e4f7fd47d", + "806ae47326d898af3e3d8f65fa6e1da029c34348", [] ], "kind-of-widget-fallback-input-search-border-inline-end-color-001.html.ini": [ @@ -351146,7 +351484,7 @@ [] ], "kind-of-widget-fallback-input-search-border-top-style-001.html.ini": [ - "3802eb169924b89b8c9c537d6969932572e87c64", + "43ff37e2d8002971392806625386133d80bba749", [] ], "kind-of-widget-fallback-input-search-border-top-width-001.html.ini": [ @@ -351253,8 +351591,12 @@ "92638e24be92df153f5c4cc43ee035bea72cd128", [] ], + "kind-of-widget-fallback-input-search-text-border-left-width-001.html.ini": [ + "eebdec5f9f326bcf509b60e419883ac2db95dcbd", + [] + ], "kind-of-widget-fallback-input-search-text-border-right-color-001.html.ini": [ - "1bd0ccb27c4a33f0717aec210dd0ca8b20a04008", + "a819ba4dd7e2b470829d6ae960f481282db4d75a", [] ], "kind-of-widget-fallback-input-search-text-border-right-style-001.html.ini": [ @@ -351266,7 +351608,7 @@ [] ], "kind-of-widget-fallback-input-search-text-border-start-end-radius-001.html.ini": [ - "aff21c3b4884f611a30ed6f81d6fe1b0f89bfbf9", + "cc97131010b8a44ffbdf149d44fe21ec4e365b91", [] ], "kind-of-widget-fallback-input-search-text-border-start-start-radius-001.html.ini": [ @@ -351382,7 +351724,7 @@ [] ], "kind-of-widget-fallback-input-submit-border-image-width-001.html.ini": [ - "42d6729b6a13a25965c6601ad7dda652e3aabdf1", + "8c5d305a363b9905052291c8f8a17a39df42115e", [] ], "kind-of-widget-fallback-input-submit-border-inline-end-color-001.html.ini": [ @@ -351478,7 +351820,7 @@ [] ], "kind-of-widget-fallback-input-text-background-position-001.html.ini": [ - "65d5dc3e227535503d157300c5fb7b7c31be6fbb", + "5210e362c5a7047fd75ec83542994c45065dd921", [] ], "kind-of-widget-fallback-input-text-background-size-001.html.ini": [ @@ -351610,7 +351952,7 @@ [] ], "kind-of-widget-fallback-input-text-border-start-start-radius-001.html.ini": [ - "f3ae2708dda4a2e4d9eb7adcdb8d39add94d748c", + "7b971a37d1fc60c071ffbe04bfa21cba7ca4335f", [] ], "kind-of-widget-fallback-input-text-border-top-color-001.html.ini": [ @@ -351842,7 +352184,7 @@ [] ], "kind-of-widget-fallback-textarea-background-origin-001.html.ini": [ - "16e90e49ee3815772e3c0721a5f4817b6998d123", + "e0471245ae82091ac3cf88ee660dec3f9e6f118b", [] ], "kind-of-widget-fallback-textarea-background-position-001.html.ini": [ @@ -351850,7 +352192,7 @@ [] ], "kind-of-widget-fallback-textarea-background-size-001.html.ini": [ - "cfd272d3f785ed6dbaa02dc6f72a5f54c72b05d9", + "00c06eab58d44fc63ee0a3d8d387beeb64b74d8a", [] ], "kind-of-widget-fallback-textarea-border-block-end-color-001.html.ini": [ @@ -351858,7 +352200,7 @@ [] ], "kind-of-widget-fallback-textarea-border-block-end-style-001.html.ini": [ - "bcededb945e56f260fc977cd42ddfa7277453718", + "6417297626e0d4b39e906e76faa7ad05749fe611", [] ], "kind-of-widget-fallback-textarea-border-block-end-width-001.html.ini": [ @@ -351866,7 +352208,7 @@ [] ], "kind-of-widget-fallback-textarea-border-block-start-color-001.html.ini": [ - "52c13ea596c5a1a034faa2e2caecbda58be8a8b6", + "982ba8205518b77dafa1bbc1e223738b8694213e", [] ], "kind-of-widget-fallback-textarea-border-block-start-style-001.html.ini": [ @@ -351882,7 +352224,7 @@ [] ], "kind-of-widget-fallback-textarea-border-bottom-left-radius-001.html.ini": [ - "9908b87169f0a3b301d745f1cdee5efb499250e8", + "370cb633d2494160c6bb3852ee4f1001e8e83261", [] ], "kind-of-widget-fallback-textarea-border-bottom-right-radius-001.html.ini": [ @@ -351894,7 +352236,7 @@ [] ], "kind-of-widget-fallback-textarea-border-bottom-width-001.html.ini": [ - "ec7a7ee72207f1e2176ddeb1dfe136fa0e82614c", + "ef5066cf76a25882631443fe36c7a5f29eb36b5c", [] ], "kind-of-widget-fallback-textarea-border-end-end-radius-001.html.ini": [ @@ -351922,7 +352264,7 @@ [] ], "kind-of-widget-fallback-textarea-border-image-width-001.html.ini": [ - "afe0cb8cfde98b1eedef995bdb21a5a55a1f2dad", + "ebbc272b596081a7fc9d458882d1d3a4569aaf68", [] ], "kind-of-widget-fallback-textarea-border-inline-end-color-001.html.ini": [ @@ -355295,7 +355637,7 @@ [] ], "abs-pos-non-replaced-vrl-006.xht.ini": [ - "1cf7e9cb10e0227e43abf6af64594587260750a9", + "30383b959149243b0342d66f8c40407c4479604b", [] ], "abs-pos-non-replaced-vrl-012-ref.xht": [ @@ -355374,6 +355716,10 @@ "c9a924e572d2b46e52ddd6a0a918dd3c304e04dd", [] ], + "abs-pos-non-replaced-vrl-138.xht.ini": [ + "6d0301b4a4db8353c55e7e54e6851ea37c093229", + [] + ], "abs-pos-non-replaced-vrl-142.xht.ini": [ "0ac184b80fddc00b38ffb930a6a48efed19e222f", [] @@ -355811,7 +356157,7 @@ [] ], "direction-vlr-003.xht.ini": [ - "683dee136615a39c6d95ea235e31e84e92067674", + "a3d46adbfa7ae2688eb13f32cbeeade3611d80dd", [] ], "direction-vrl-002-ref.xht": [ @@ -356032,6 +356378,10 @@ "24130d157837083bf97eeff3420f58693f1daf27", [] ], + "horizontal-rule-vlr-003.xht.ini": [ + "abe4294f70c47d6e15948b5b0b9dcffff1661f1c", + [] + ], "horizontal-rule-vlr-005.xht.ini": [ "0cbf3deb4dc48c8a55546d6f625e5e9ccd366165", [] @@ -357202,6 +357552,10 @@ "07718ec3665d1572a93639829f45d1716baebcf2", [] ], + "sizing-orthog-prct-htb-in-vlr-001.xht.ini": [ + "aaa630977c83a346a43d6ae5d4b138c266aebb55", + [] + ], "sizing-orthog-prct-htb-in-vlr-002-ref.xht": [ "8983440cd18eb6b10a71231c7fb62e4bc1ad4ff5", [] @@ -360075,13 +360429,17 @@ [] ], "css-filters-animation-blur.html.ini": [ - "5639214ee56ad86fa1207edfae96a72f46b574b6", + "bfb3d8a692efd40c7abb069d00b23dd6079a9343", [] ], "css-filters-animation-brightness-ref.html": [ "f4d252b65b70d916f40f9c2adf9eb28885bc2a1d", [] ], + "css-filters-animation-brightness.html.ini": [ + "310a449826fbf875afb235ac652f43c920e483ec", + [] + ], "css-filters-animation-combined-001-ref.html": [ "0d4e755c68337badcb629b0e56cea63dd3cf7a47", [] @@ -360894,6 +361252,10 @@ [] ] }, + "svg-feoffset-001.html.ini": [ + "b8aa56fe6abacce7bbf0320a053b6798a89b2461", + [] + ], "svg-filter-vs-clip-path.html.ini": [ "98ff71769d9b1db22d1fbfea566838f945aeaee9", [] @@ -361042,6 +361404,10 @@ [] ] }, + "relative-units-003.html.ini": [ + "a4db1d6985d7a90bec229250b11274bc109e50f6", + [] + ], "resources": { "matchmedia-utils.js": [ "327e8f69fe27bb0d732db62d62cdc3be505ec022", @@ -362100,6 +362466,10 @@ "f5e4edf0f286fcefc15b14d7950083092cb45fb2", [] ], + "focus-within-011.html.ini": [ + "2ada8102a937cf30fcd33862c45d282e7efb63a9", + [] + ], "focus-within-012-ref.html": [ "5aeac2441d603492315b573cb748aed02fc535cd", [] @@ -362256,7 +362626,7 @@ [] ], "nth-child-in-shadow-root.html.ini": [ - "88137b2ff0f5a1ceaeca7a171adcaa0cc4d55383", + "ccf32d72dac36700b7d0cf2a41b445dfb44a82a6", [] ], "nth-child-of-attr-largedom-ref.html": [ @@ -362328,7 +362698,7 @@ [] ], "nth-last-child-in-shadow-root.html.ini": [ - "964443bf54333664cde4d4fd49aa496a6d73031a", + "2aed680aa6a48a4be467d0fce2509ee6e2234ebf", [] ], "nth-last-child-of-attr-ref.html": [ @@ -362376,6 +362746,22 @@ [] ] }, + "is-where-error-recovery-expected.txt": [ + "f2ba708f07acfed89cab2afc57b97413c5ba0f9d", + [] + ], + "is-where-error-recovery.html.ini": [ + "f459e2c2cfe52e9748285588267acc74cac0f712", + [] + ], + "is-where-parsing-expected.txt": [ + "6d7d5fce9feac09600668b39fcfe8613c584013c", + [] + ], + "is-where-parsing.html.ini": [ + "dde6c5acc6a5f046fccf15586590387e7860f340", + [] + ], "is-where-pseudo-elements-ref.html": [ "3a17efedfbbaa0558ab73a16aef42ce626e27b7d", [] @@ -362434,6 +362820,10 @@ "60bf0446e636ef47c4f3a2aa31178898ee588feb", [] ], + "nth-child-of-complex-selector-many-children-2.html.ini": [ + "21d86f49b67e1fd09c71d697f96ce3bc4ae3fd79", + [] + ], "nth-child-of-complex-selector-many-children-ref.html": [ "031f3f1785019cb73fe25dd739a36a5a248d0b8a", [] @@ -362526,6 +362916,10 @@ "cda1902d1a64a96f35bbe07fd06288bc63b5e732", [] ], + "nth-last-child-of-complex-selector.html.ini": [ + "1c12522cce7b6b4d5de1d4fd090c8137f151ecbd", + [] + ], "nth-last-child-of-compound-selector-ref.html": [ "bc5c3ddbd56ceb0f9489c1ab5c574955c4fd5655", [] @@ -362810,6 +363204,24 @@ ] } }, + "parsing": { + "parse-has-disallow-nesting-has-inside-has-expected.txt": [ + "66284e6503ba5ef9e5c40bb03dae950a1013c2e6", + [] + ], + "parse-has-disallow-nesting-has-inside-has.html.ini": [ + "c53ede9c024e5b046809de598632490d4a1a2dad", + [] + ], + "parse-has-expected.txt": [ + "e4c0d3a5aa9b38503c102338b6f547e310ebdcd9", + [] + ], + "parse-has.html.ini": [ + "277f97a6b679589dd5c9518deadad8dce59dca1c", + [] + ] + }, "remove-hovered-element-ref.html": [ "9527a465ea64cb106704baf3e45647d97239ef5b", [] @@ -363355,7 +363767,7 @@ [] ], "ElementInternals-target-element-is-held-strongly.html.ini": [ - "580caad4b03496d7b6d89e8234f228bf390d8619", + "dc795c3a27b80c8f5cb468cdcc802337671f6369", [] ], "ElementInternals-validation.html.ini": [ @@ -364430,7 +364842,7 @@ [] ], "passive-touchstart-event-listener-on-window.html.ini": [ - "64d3e8b375aec65f559781c7e1d2f906ad4bd29c", + "6cf0854fb15432cc4f40039af685e177ef055f94", [] ], "passive-wheel-event-listener-on-body.html.ini": [ @@ -364508,7 +364920,7 @@ [] ], "overscroll-deltas.html.ini": [ - "fa86d27fbc4f1b084620e6fb4f00e691c95133b7", + "88a71b0fb6bb764a2b85aae20a020240922238f9", [] ], "overscroll-event-fired-to-scrolled-element.html.ini": [ @@ -364569,7 +364981,7 @@ ] }, "webkit-animation-iteration-event.html.ini": [ - "529abf2b0a02ca82533a80ce212aecbae1f48b05", + "f60214a18a1da1c587d54bf2548cc1c07cbcf7aa", [] ] }, @@ -366702,7 +367114,7 @@ [] ], "disconnect-image.html.ini": [ - "28a97ad7c87547c1d4b2a59a5b2c6972b10684a4", + "4b8f9f8859c368efed73539f06c5b9bad0c50d14", [] ], "first-letter-background.html.ini": [ @@ -386635,11 +387047,11 @@ [] ], "search-styles-expected.txt": [ - "f9926c87deae93bd2ab0cef146a5664a4c0a51c2", + "c2dea86d7696d1877c3ca7654ae7bd3a570a4cfd", [] ], "search-styles-iso-8859-8-expected.txt": [ - "373390bd7a7368a4f5e848445af2df40db06d0ca", + "afd37c3f13053f1e11067bcd61d8f0d6bc2c2731", [] ], "search-styles-iso-8859-8.html.ini": [ @@ -387137,7 +387549,7 @@ [] ], "fieldset-transform-translatez.html.ini": [ - "e9263877d705fe1ac1b6616750011b6c8c0c30c9", + "9572544fa976e4be0a98aefc3c2008470f70a95e", [] ], "fieldset-vertical-ref.html": [ @@ -387499,7 +387911,7 @@ [] ], "img-dim.html.ini": [ - "d6a1c3a1d76e7402f0e8f0c089377900d934dd41", + "d78081e30e654a8cc93fad815ac6494049607faf", [] ], "img_border-ref.xhtml": [ @@ -387981,7 +388393,7 @@ [] ], "option-only-label.html.ini": [ - "c2de9ed80a1ca698994c2f38debf8e37bca94645", + "60c4b4ea8625328244f575117329d80427d8dcaa", [] ], "option-rm-label.html.ini": [ @@ -394215,18 +394627,6 @@ "6786df7eb08f41cd1efc6654f4934dce000ce9af", [] ], - "html5lib_search-element_run_type=uri-expected.txt": [ - "67fc20118f9671fdaf810466437948ad374f85fb", - [] - ], - "html5lib_search-element_run_type=write-expected.txt": [ - "67fc20118f9671fdaf810466437948ad374f85fb", - [] - ], - "html5lib_search-element_run_type=write_single-expected.txt": [ - "67fc20118f9671fdaf810466437948ad374f85fb", - [] - ], "html5lib_template-expected.txt": [ "332024e03e79a98244ffe767051d100a3cde764b", [] @@ -399161,6 +399561,10 @@ "faa3ca5de5efa186a7734dc1731be23b206b5689", [] ], + "observe-svg-data-uri-background-image.html.ini": [ + "2a2a6d0bdaa540a45766f2d3b65271f3ef2aeb23", + [] + ], "observe-svg-data-uri-image.html.ini": [ "6bf4ad7e50fce9b6bb39f7eaac799414dc74a13f", [] @@ -400359,6 +400763,10 @@ "ba9c660bedea2eaf402c71cea1f270880ecdff64", [] ], + "stretchy-munderover-2d.html.ini": [ + "e90a33659142b6bd06546c0f4a8d370b08403a90", + [] + ], "stretchy-munderover-2g.html.ini": [ "5f6cb41280bcb8299e5a50f8561983ed2de6ba36", [] @@ -401898,6 +402306,16 @@ ] } }, + "mediacapture-extensions": { + "MediaStreamTrack-getFrameStats.https-expected.txt": [ + "31c8b4dde42997bf4dafad51d55c614b1b99aa66", + [] + ], + "MediaStreamTrack-getFrameStats.https.html.ini": [ + "480251407b18c050371aa63d8411d25b04325f57", + [] + ] + }, "mediacapture-fromelement": { "META.yml": [ "17195009fa1fa8f2b5adbda12c872f3a869bc297", @@ -405211,7 +405629,7 @@ [] ], "pointerevent_mouseevent_key_pressed.html.ini": [ - "982f5a22ee581d2b84507c404e8de062cbd8d1cc", + "36fcef16d4baffe345714e8cdca1ac7a2ff5388d", [] ], "pointerevent_touch-action_two-finger_interaction.html.ini": [ @@ -415912,7 +416330,7 @@ [] ], "will-change-in-transformed-foreign-object.html.ini": [ - "85b4ad5d2edaf69480e6c7893ef4cea2c047214d", + "8e65914c516b576efe490aa04de690d9c0854199", [] ] } @@ -416200,7 +416618,7 @@ [] ], "view-viewbox-override.html.ini": [ - "60ada3c5454a41f302baf78e73617fb55bafac9f", + "c6576d68dfee91947de762d0e9d912de36a03c28", [] ] }, @@ -480170,21 +480588,21 @@ ] ], "baseline-source-first-textarea-001.tentative.html": [ - "ccae10cec8a2a3afe3066d498c6892a095645a1d", + "02b2051c9ef112af326db248454d6378f1cd5e08", [ null, {} ] ], "baseline-source-first-textarea-002.tentative.html": [ - "6c2b0a2d412d727a5bb31ba5dfde7bfdcae1f70a", + "633ba615e746401eb756d8e003046bec79274ff6", [ null, {} ] ], "baseline-source-first-textarea-003.tentative.html": [ - "0de72e8bbdf6bf3cf8caddf74f06ce8311467739", + "098b05bd1c6ea8d728b0c14a530c426eb418c16d", [ null, {} @@ -480219,21 +480637,21 @@ ] ], "baseline-source-last-textarea-001.tentative.html": [ - "c6e45a4d20bd7dfcaca6e43c6b31c098a9aaeee1", + "156c007233ccf90df3540c7750470719cc229771", [ null, {} ] ], "baseline-source-last-textarea-002.tentative.html": [ - "6bba60070ba86391bc19b75d836b6ecaeb97b061", + "d8b20b35ebd82a4f04e62a04de8dce9e6b60b348", [ null, {} ] ], "baseline-source-last-textarea-003.tentative.html": [ - "45fb2a40e432edf20481a89959e6621caa069912", + "c80d464a10104ad3d7cb458d8916b2ff3f556994", [ null, {} @@ -484523,7 +484941,7 @@ ] ], "slotted-parsing.html": [ - "25b003091fadb004b90f52ec1ba4179bf1a220c8", + "bed4dedd56072ad1937ec536f2e6bc04f1fb70fd", [ null, {} @@ -502163,7 +502581,7 @@ ] ], "is-where-error-recovery.html": [ - "1d6e870ed23e5b65ea1bded940c6531b1da18a51", + "f7e6f6ff6ea0b6058b6f397386e44611ad2a6762", [ null, {} @@ -502177,7 +502595,7 @@ ] ], "is-where-parsing.html": [ - "c9cc2236a3281a03589257afd7d7b060919956b7", + "3159ecfe6a65b715912eeee10041b56defc89416", [ null, {} @@ -502324,14 +502742,14 @@ ] ], "parse-has-disallow-nesting-has-inside-has.html": [ - "9cf989fcbeb7409208199e5ad96211a353e847e7", + "c5fcb589a3ccf1af1b46f8c004c37e34e8946263", [ null, {} ] ], "parse-has.html": [ - "5d071010b42f9b726490b53cfeb8da75b20159ad", + "902fc98ff68350e3a6c8c64e3cc62bf17f2e6e47", [ null, {} @@ -527055,7 +527473,7 @@ ] ], "script-focus.https.html": [ - "4b5fe758b48d12d2a16bb47ff0d50d3fc4c21b47", + "0bef98219bd192851b30a9f8dbd61fe17c724962", [ null, { @@ -606869,6 +607287,13 @@ null, {} ] + ], + "MediaStreamTrack-getFrameStats.https.html": [ + "dc8a172a5f41b80352e60eb3183719cafd6e02db", + [ + null, + {} + ] ] }, "mediacapture-fromelement": { @@ -710514,34 +710939,6 @@ {} ] ], - "border-image-13.html": [ - "52095cf289f8a46d348d08611e52c12e1731e676", - [ - null, - {} - ] - ], - "border-image-14.html": [ - "119d3309e97ed2912ba7317829c94395adcf72fa", - [ - null, - {} - ] - ], - "border-image-15.html": [ - "a3e748e6f56339482f7bbe929b82283150ab1129", - [ - null, - {} - ] - ], - "border-image-16.html": [ - "6829c04ffeb2a179e06087bb4745636ec90ec083", - [ - null, - {} - ] - ], "border-image-image-type-001.htm": [ "ebc152fe74bd165b68cda0953ce3ef5545aa998d", [ @@ -710605,41 +711002,6 @@ {} ] ], - "border-image-slice-006.htm": [ - "d6484b06db30b052f584c60f0dd3dbb20a61ae41", - [ - null, - {} - ] - ], - "border-image-width-001.htm": [ - "a07426a6282f446d1c7bb31a93b098bd2c102470", - [ - null, - {} - ] - ], - "border-image-width-002.htm": [ - "3503e8373fcf69dcbf121913c7df24e583afc1f2", - [ - null, - {} - ] - ], - "border-image-width-003.htm": [ - "d85c6352885c6a51634eb49b3360fc4726cacb7c", - [ - null, - {} - ] - ], - "border-image-width-004.htm": [ - "0d1fc02f141bb4abd3cb929f4bdd38f68209d720", - [ - null, - {} - ] - ], "border-images.html": [ "930a1df3b7fda4098f36cc9691a544f46e2311d5", [
diff --git a/third_party/blink/web_tests/external/wpt/compat/webkit-text-fill-color-property-003.html.ini b/third_party/blink/web_tests/external/wpt/compat/webkit-text-fill-color-property-003.html.ini new file mode 100644 index 0000000..346430e --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/compat/webkit-text-fill-color-property-003.html.ini
@@ -0,0 +1,3 @@ +[webkit-text-fill-color-property-003.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/cookies/attributes/attributes-ctl.sub-expected.txt b/third_party/blink/web_tests/external/wpt/cookies/attributes/attributes-ctl.sub-expected.txt index caa52f3..dfb0135 100644 --- a/third_party/blink/web_tests/external/wpt/cookies/attributes/attributes-ctl.sub-expected.txt +++ b/third_party/blink/web_tests/external/wpt/cookies/attributes/attributes-ctl.sub-expected.txt
@@ -1,18 +1,18 @@ This is a testharness.js-based test. -Found 429 tests; 391 PASS, 38 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 429 tests; 421 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN. PASS Cookie with %x0 in Domain attribute value is handled correctly. -FAIL Cookie with %x0 after Domain attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test0domain2=t" +PASS Cookie with %x0 after Domain attribute value is handled correctly. PASS Cookie with %x0 in Path attribute value is handled correctly. -FAIL Cookie with %x0 after Path attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test0path2=t" -FAIL Cookie with %x0 in Max-Age attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test0maxage=t" -FAIL Cookie with %x0 after Max-Age attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test0maxage2=t" -FAIL Cookie with %x0 in Expires attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test0expires=t" -FAIL Cookie with %x0 after Expires attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test0expires2=t" -FAIL Cookie with %x0 in Secure attribute is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test0secure=t" +PASS Cookie with %x0 after Path attribute value is handled correctly. +PASS Cookie with %x0 in Max-Age attribute value is handled correctly. +PASS Cookie with %x0 after Max-Age attribute value is handled correctly. +PASS Cookie with %x0 in Expires attribute value is handled correctly. +PASS Cookie with %x0 after Expires attribute value is handled correctly. +PASS Cookie with %x0 in Secure attribute is handled correctly. PASS Cookie with %x0 after Secure attribute is handled correctly. -FAIL Cookie with %x0 in HttpOnly attribute is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test0httponly=t" -FAIL Cookie with %x0 in SameSite attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test0samesite=t" -FAIL Cookie with %x0 after SameSite attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test0samesite2=t" +PASS Cookie with %x0 in HttpOnly attribute is handled correctly. +PASS Cookie with %x0 in SameSite attribute value is handled correctly. +PASS Cookie with %x0 after SameSite attribute value is handled correctly. PASS Cookie with %x1 in Domain attribute value is handled correctly. PASS Cookie with %x1 after Domain attribute value is handled correctly. PASS Cookie with %x1 in Path attribute value is handled correctly. @@ -131,18 +131,18 @@ FAIL Cookie with %x9 in SameSite attribute value is handled correctly. assert_equals: The cookie was set as expected. expected "test9samesite=t" but got "" PASS Cookie with %x9 after SameSite attribute value is handled correctly. PASS Cookie with %xa in Domain attribute value is handled correctly. -FAIL Cookie with %xa after Domain attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test10domain2=t" +PASS Cookie with %xa after Domain attribute value is handled correctly. PASS Cookie with %xa in Path attribute value is handled correctly. -FAIL Cookie with %xa after Path attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test10path2=t" -FAIL Cookie with %xa in Max-Age attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test10maxage=t" -FAIL Cookie with %xa after Max-Age attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test10maxage2=t" -FAIL Cookie with %xa in Expires attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test10expires=t" -FAIL Cookie with %xa after Expires attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test10expires2=t" -FAIL Cookie with %xa in Secure attribute is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test10secure=t" +PASS Cookie with %xa after Path attribute value is handled correctly. +PASS Cookie with %xa in Max-Age attribute value is handled correctly. +PASS Cookie with %xa after Max-Age attribute value is handled correctly. +PASS Cookie with %xa in Expires attribute value is handled correctly. +PASS Cookie with %xa after Expires attribute value is handled correctly. +PASS Cookie with %xa in Secure attribute is handled correctly. PASS Cookie with %xa after Secure attribute is handled correctly. -FAIL Cookie with %xa in HttpOnly attribute is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test10httponly=t" -FAIL Cookie with %xa in SameSite attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test10samesite=t" -FAIL Cookie with %xa after SameSite attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test10samesite2=t" +PASS Cookie with %xa in HttpOnly attribute is handled correctly. +PASS Cookie with %xa in SameSite attribute value is handled correctly. +PASS Cookie with %xa after SameSite attribute value is handled correctly. PASS Cookie with %xb in Domain attribute value is handled correctly. PASS Cookie with %xb after Domain attribute value is handled correctly. PASS Cookie with %xb in Path attribute value is handled correctly. @@ -170,18 +170,18 @@ PASS Cookie with %xc in SameSite attribute value is handled correctly. PASS Cookie with %xc after SameSite attribute value is handled correctly. PASS Cookie with %xd in Domain attribute value is handled correctly. -FAIL Cookie with %xd after Domain attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test13domain2=t" +PASS Cookie with %xd after Domain attribute value is handled correctly. PASS Cookie with %xd in Path attribute value is handled correctly. -FAIL Cookie with %xd after Path attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test13path2=t" -FAIL Cookie with %xd in Max-Age attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test13maxage=t" -FAIL Cookie with %xd after Max-Age attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test13maxage2=t" -FAIL Cookie with %xd in Expires attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test13expires=t" -FAIL Cookie with %xd after Expires attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test13expires2=t" -FAIL Cookie with %xd in Secure attribute is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test13secure=t" +PASS Cookie with %xd after Path attribute value is handled correctly. +PASS Cookie with %xd in Max-Age attribute value is handled correctly. +PASS Cookie with %xd after Max-Age attribute value is handled correctly. +PASS Cookie with %xd in Expires attribute value is handled correctly. +PASS Cookie with %xd after Expires attribute value is handled correctly. +PASS Cookie with %xd in Secure attribute is handled correctly. PASS Cookie with %xd after Secure attribute is handled correctly. -FAIL Cookie with %xd in HttpOnly attribute is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test13httponly=t" -FAIL Cookie with %xd in SameSite attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test13samesite=t" -FAIL Cookie with %xd after SameSite attribute value is handled correctly. assert_equals: The cookie was rejected. expected "" but got "test13samesite2=t" +PASS Cookie with %xd in HttpOnly attribute is handled correctly. +PASS Cookie with %xd in SameSite attribute value is handled correctly. +PASS Cookie with %xd after SameSite attribute value is handled correctly. PASS Cookie with %xe in Domain attribute value is handled correctly. PASS Cookie with %xe after Domain attribute value is handled correctly. PASS Cookie with %xe in Path attribute value is handled correctly.
diff --git a/third_party/blink/web_tests/external/wpt/cookies/name/name-ctl-expected.txt b/third_party/blink/web_tests/external/wpt/cookies/name/name-ctl-expected.txt index d3d31d9..ef670efc 100644 --- a/third_party/blink/web_tests/external/wpt/cookies/name/name-ctl-expected.txt +++ b/third_party/blink/web_tests/external/wpt/cookies/name/name-ctl-expected.txt
@@ -1,6 +1,6 @@ This is a testharness.js-based test. -Found 66 tests; 59 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN. -FAIL Cookie with %x0 in name is rejected (DOM). assert_equals: The cookie was rejected. expected "" but got "test0" +Found 66 tests; 62 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN. +PASS Cookie with %x0 in name is rejected (DOM). PASS Cookie with %x1 in name is rejected (DOM). PASS Cookie with %x2 in name is rejected (DOM). PASS Cookie with %x3 in name is rejected (DOM). @@ -10,10 +10,10 @@ PASS Cookie with %x7 in name is rejected (DOM). PASS Cookie with %x8 in name is rejected (DOM). FAIL Cookie with %x9 in name is accepted (DOM). assert_equals: The cookie was set as expected. expected "test9\tname=9" but got "" -FAIL Cookie with %xa in name is rejected (DOM). assert_equals: The cookie was rejected. expected "" but got "test10" +PASS Cookie with %xa in name is rejected (DOM). PASS Cookie with %xb in name is rejected (DOM). PASS Cookie with %xc in name is rejected (DOM). -FAIL Cookie with %xd in name is rejected (DOM). assert_equals: The cookie was rejected. expected "" but got "test13" +PASS Cookie with %xd in name is rejected (DOM). PASS Cookie with %xe in name is rejected (DOM). PASS Cookie with %xf in name is rejected (DOM). PASS Cookie with %x10 in name is rejected (DOM).
diff --git a/third_party/blink/web_tests/external/wpt/cookies/value/value-ctl-expected.txt b/third_party/blink/web_tests/external/wpt/cookies/value/value-ctl-expected.txt index e308177..0436b5c3 100644 --- a/third_party/blink/web_tests/external/wpt/cookies/value/value-ctl-expected.txt +++ b/third_party/blink/web_tests/external/wpt/cookies/value/value-ctl-expected.txt
@@ -1,6 +1,6 @@ This is a testharness.js-based test. -Found 66 tests; 59 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN. -FAIL Cookie with %x0 in value is rejected (DOM). assert_equals: The cookie was rejected. expected "" but got "test=0" +Found 66 tests; 62 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN. +PASS Cookie with %x0 in value is rejected (DOM). PASS Cookie with %x1 in value is rejected (DOM). PASS Cookie with %x2 in value is rejected (DOM). PASS Cookie with %x3 in value is rejected (DOM). @@ -10,10 +10,10 @@ PASS Cookie with %x7 in value is rejected (DOM). PASS Cookie with %x8 in value is rejected (DOM). FAIL Cookie with %x9 in value is accepted (DOM). assert_equals: The cookie was set as expected. expected "test=9\tvalue" but got "" -FAIL Cookie with %xa in value is rejected (DOM). assert_equals: The cookie was rejected. expected "" but got "test=10" +PASS Cookie with %xa in value is rejected (DOM). PASS Cookie with %xb in value is rejected (DOM). PASS Cookie with %xc in value is rejected (DOM). -FAIL Cookie with %xd in value is rejected (DOM). assert_equals: The cookie was rejected. expected "" but got "test=13" +PASS Cookie with %xd in value is rejected (DOM). PASS Cookie with %xe in value is rejected (DOM). PASS Cookie with %xf in value is rejected (DOM). PASS Cookie with %x10 in value is rejected (DOM).
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-026.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-026.xht.ini new file mode 100644 index 0000000..bce968a7 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-026.xht.ini
@@ -0,0 +1,3 @@ +[background-026.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-038.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-038.xht.ini index d639d0da..3103bea 100644 --- a/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-038.xht.ini +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-038.xht.ini
@@ -1,3 +1,4 @@ [background-038.xht] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-041.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-041.xht.ini index 7fde644..93596c3 100644 --- a/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-041.xht.ini +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-041.xht.ini
@@ -1,3 +1,4 @@ [background-041.xht] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-applies-to-005.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-applies-to-005.xht.ini new file mode 100644 index 0000000..0e98221 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-applies-to-005.xht.ini
@@ -0,0 +1,3 @@ +[background-applies-to-005.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-applies-to-006.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-applies-to-006.xht.ini new file mode 100644 index 0000000..ee6f4ef --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-applies-to-006.xht.ini
@@ -0,0 +1,3 @@ +[background-applies-to-006.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-attachment-applies-to-009.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-attachment-applies-to-009.xht.ini new file mode 100644 index 0000000..691d4cbe --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-attachment-applies-to-009.xht.ini
@@ -0,0 +1,3 @@ +[background-attachment-applies-to-009.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-image-applies-to-009.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-image-applies-to-009.xht.ini new file mode 100644 index 0000000..ae1acd85 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/backgrounds/background-image-applies-to-009.xht.ini
@@ -0,0 +1,3 @@ +[background-image-applies-to-009.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-bottom-color-013.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-bottom-color-013.xht.ini new file mode 100644 index 0000000..8fc0e7ca --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-bottom-color-013.xht.ini
@@ -0,0 +1,3 @@ +[border-bottom-color-013.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-left-color-036.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-left-color-036.xht.ini new file mode 100644 index 0000000..2ad3f7b5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-left-color-036.xht.ini
@@ -0,0 +1,3 @@ +[border-left-color-036.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-058.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-058.xht.ini new file mode 100644 index 0000000..3796117 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-058.xht.ini
@@ -0,0 +1,3 @@ +[border-right-color-058.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-072.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-072.xht.ini new file mode 100644 index 0000000..375b145c --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-072.xht.ini
@@ -0,0 +1,3 @@ +[border-right-color-072.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-109.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-109.xht.ini new file mode 100644 index 0000000..9f83c35 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-109.xht.ini
@@ -0,0 +1,3 @@ +[border-right-color-109.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-130.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-130.xht.ini new file mode 100644 index 0000000..13e6b33d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-color-130.xht.ini
@@ -0,0 +1,3 @@ +[border-right-color-130.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-width-applies-to-013.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-width-applies-to-013.xht.ini new file mode 100644 index 0000000..df3990d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-right-width-applies-to-013.xht.ini
@@ -0,0 +1,3 @@ +[border-right-width-applies-to-013.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-color-041.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-color-041.xht.ini new file mode 100644 index 0000000..224272a5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-color-041.xht.ini
@@ -0,0 +1,3 @@ +[border-top-color-041.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-width-084.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-width-084.xht.ini new file mode 100644 index 0000000..cda6798 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-width-084.xht.ini
@@ -0,0 +1,3 @@ +[border-top-width-084.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-width-applies-to-005.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-width-applies-to-005.xht.ini new file mode 100644 index 0000000..50cca6f --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-width-applies-to-005.xht.ini
@@ -0,0 +1,3 @@ +[border-top-width-applies-to-005.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-width-applies-to-012.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-width-applies-to-012.xht.ini new file mode 100644 index 0000000..c683b9fc --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/borders/border-top-width-applies-to-012.xht.ini
@@ -0,0 +1,3 @@ +[border-top-width-applies-to-012.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/linebox/line-height-applies-to-003.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/linebox/line-height-applies-to-003.xht.ini new file mode 100644 index 0000000..ac3165a --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/linebox/line-height-applies-to-003.xht.ini
@@ -0,0 +1,3 @@ +[line-height-applies-to-003.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/block-formatting-context-height-001.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/block-formatting-context-height-001.xht.ini new file mode 100644 index 0000000..a6b78fa --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/block-formatting-context-height-001.xht.ini
@@ -0,0 +1,3 @@ +[block-formatting-context-height-001.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/height-applies-to-015.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/height-applies-to-015.xht.ini new file mode 100644 index 0000000..1fd3a36 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/height-applies-to-015.xht.ini
@@ -0,0 +1,3 @@ +[height-applies-to-015.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/max-height-067.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/max-height-067.xht.ini new file mode 100644 index 0000000..0aa0fe0d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/max-height-067.xht.ini
@@ -0,0 +1,3 @@ +[max-height-067.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/max-width-103.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/max-width-103.xht.ini new file mode 100644 index 0000000..b3215c9 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/max-width-103.xht.ini
@@ -0,0 +1,3 @@ +[max-width-103.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-non-replaced-height-005.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-non-replaced-height-005.xht.ini new file mode 100644 index 0000000..8662e2bc --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-non-replaced-height-005.xht.ini
@@ -0,0 +1,3 @@ +[absolute-non-replaced-height-005.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-non-replaced-height-009.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-non-replaced-height-009.xht.ini new file mode 100644 index 0000000..7204b66 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-non-replaced-height-009.xht.ini
@@ -0,0 +1,3 @@ +[absolute-non-replaced-height-009.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-non-replaced-width-001.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-non-replaced-width-001.xht.ini index 7078ebe..8aba81b0 100644 --- a/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-non-replaced-width-001.xht.ini +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-non-replaced-width-001.xht.ini
@@ -1,3 +1,4 @@ [absolute-non-replaced-width-001.xht] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-replaced-height-030.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-replaced-height-030.xht.ini index b2702e2..7ff4d4b 100644 --- a/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-replaced-height-030.xht.ini +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/absolute-replaced-height-030.xht.ini
@@ -1,3 +1,4 @@ [absolute-replaced-height-030.xht] expected: if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/left-applies-to-007.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/left-applies-to-007.xht.ini new file mode 100644 index 0000000..200d2a5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/positioning/left-applies-to-007.xht.ini
@@ -0,0 +1,3 @@ +[left-applies-to-007.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/border-collapse-dynamic-column-002.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/border-collapse-dynamic-column-002.xht.ini new file mode 100644 index 0000000..0ab8768 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/border-collapse-dynamic-column-002.xht.ini
@@ -0,0 +1,3 @@ +[border-collapse-dynamic-column-002.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/fixed-table-layout-018.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/fixed-table-layout-018.xht.ini new file mode 100644 index 0000000..ed7a1cd --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/fixed-table-layout-018.xht.ini
@@ -0,0 +1,3 @@ +[fixed-table-layout-018.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/fixed-table-layout-030.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/fixed-table-layout-030.xht.ini new file mode 100644 index 0000000..2b291cc7 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/fixed-table-layout-030.xht.ini
@@ -0,0 +1,3 @@ +[fixed-table-layout-030.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-visual-layout-017.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-visual-layout-017.xht.ini new file mode 100644 index 0000000..5170a6f7 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-visual-layout-017.xht.ini
@@ -0,0 +1,3 @@ +[table-visual-layout-017.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/text/white-space-processing-017.xht.ini b/third_party/blink/web_tests/external/wpt/css/CSS2/text/white-space-processing-017.xht.ini new file mode 100644 index 0000000..e2dada1 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/CSS2/text/white-space-processing-017.xht.ini
@@ -0,0 +1,3 @@ +[white-space-processing-017.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-attachment-local/attachment-local-clipping-color-1.html.ini b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-attachment-local/attachment-local-clipping-color-1.html.ini new file mode 100644 index 0000000..ea0a7bc --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-attachment-local/attachment-local-clipping-color-1.html.ini
@@ -0,0 +1,3 @@ +[attachment-local-clipping-color-1.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-size-with-negative-value.html.ini b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-size-with-negative-value.html.ini new file mode 100644 index 0000000..6425340 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-size-with-negative-value.html.ini
@@ -0,0 +1,3 @@ +[background-size-with-negative-value.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-13.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-013.html similarity index 67% rename from third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-13.html rename to third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-013.html index 52095cf2..c7cc7d5 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-13.html +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-013.html
@@ -3,13 +3,13 @@ <head> <meta charset="utf-8"> <title> - CSS Border and Background: border-image #13 border-image-repeat + CSS Backgrounds and Borders Test: border-image-repeat: repeat (basic) </title> - <meta name="assert" content="Test for the border-image-repeat property with the value repeat" /> - <link rel="author" title="Jérémie Patonnier" href="mailto:jeremie@patonnier.net" / > - <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-image-repeat"> + <link rel="author" title="Jérémie Patonnier" href="mailto:jeremie@patonnier.net"> + <link rel="match" href="reference/border-image-013-ref.html"> + <link rel="help" href="http://www.w3.org/TR/css3-background/#border-image-repeat"> <style type="text/css"> @@ -29,7 +29,7 @@ <body> <p> - Pass if the square has borders made of green dots. + Pass if the square has borders made of green dots and <strong>no red</strong>. </p> <div id="test"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-14.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-14.html deleted file mode 100644 index 119d330..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-14.html +++ /dev/null
@@ -1,38 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title> - CSS Border and Background: border-image #14 border-image-repeat - </title> - <meta name="assert" content="Test for the border-image-repeat property with the value round" /> - - <link rel="author" title="Jérémie Patonnier" href="mailto:jeremie@patonnier.net" / > - - <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-image-repeat"> - - <style type="text/css"> - - #test { - border : 10px solid red; - width : 39px; - height : 39px; - - border-image-source: url(support/img-ref-2.png); - border-image-slice : 10; - border-image-repeat: round; - } - - </style> - -</head> -<body> - - <p> - Pass if the square has borders made of plain green dots. - </p> - - <div id="test"></div> - -</body> -</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-15.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-15.html deleted file mode 100644 index a3e748e6..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-15.html +++ /dev/null
@@ -1,38 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title> - CSS Border and Background: border-image #15 border-image-repeat - </title> - <meta name="assert" content="Test for the border-image-repeat property with the value space" /> - - <link rel="author" title="Jérémie Patonnier" href="mailto:jeremie@patonnier.net" / > - - <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-image-repeat"> - - <style type="text/css"> - - #test { - border : 10px solid red; - width : 39px; - height : 39px; - - border-image-source: url(support/img-ref-2.png); - border-image-slice : 10; - border-image-repeat: space; - } - - </style> - -</head> -<body> - - <p> - Pass if the square has borders made of plain regular green dots. - </p> - - <div id="test"></div> - -</body> -</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-16.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-16.html deleted file mode 100644 index 6829c04..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-16.html +++ /dev/null
@@ -1,38 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title> - CSS Border and Background: border-image #16 border-image-repeat - </title> - <meta name="assert" content="Test for the border-image-repeat property with the value stretch" /> - - <link rel="author" title="Jérémie Patonnier" href="mailto:jeremie@patonnier.net" / > - - <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-image-repeat"> - - <style type="text/css"> - - #test { - border : 10px solid red; - width : 39px; - height : 39px; - - border-image-source: url(support/img-ref-2.png); - border-image-slice : 10; - border-image-repeat: stretch; - } - - </style> - -</head> -<body> - - <p> - Pass if the the corners are regular green dots and borders are single stretch dots. - </p> - - <div id="test"></div> - -</body> -</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-003.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-003.html new file mode 100644 index 0000000..c2cb237 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-003.html
@@ -0,0 +1,110 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Backgrounds Test: border-image-repeat: round</title> + + <!-- + + Created: June 21st 2023 + + Last modified: June 25th 2023 + + --> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + <link rel="help" href="https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat"> + <link rel="help" href="https://www.w3.org/TR/css-backgrounds-3/#border-image-process"> + <link rel="match" href="reference/border-image-repeat-round-003-ref.html"> + + <meta content="" name="flags"> + <meta content="This test checks that the process of repeating the tile when 'border-image-repeat' is 'round'. In this test, we check with a specially crafted rectangular border-image how the sliced border-image is, in the first-subtest, rescaled down in the 4 sides, is, in the second-subtest, rescaled up in the 4 sides and, finally, is, in the third-subtest, rescaled up in the left and right sides while being rescaled down in the top and bottom sides." name="assert"> + + <!-- + + 'round' + The image is tiled (repeated) to fill the area. If it does not + fill the area with a whole number of tiles, the image is rescaled + so that it does. + + https://www.w3.org/TR/css-backgrounds-3/#valdef-border-image-repeat-round + + --> + + <style> + div + { + border: red solid 64px; + border-image-repeat: round; /* this is the same as 'round round' since + " + If the second keyword is absent, it is assumed to be the same as the first. + https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat + " + */ + border-image-slice: 64 fill; /* the center will be black */ + border-image-source: url("support/4bicolor-squares.png"); + display: inline-block; + margin-right: 1em; + vertical-align: bottom; + } + + div#first-subtest + { + height: 96px; + width: 96px; + /* + 96px divided by 64px == 1.5 which is rounded up to 2. + So, each image should be 96px divided by 2 == 48px + wide or tall. That means that the top and bottom + bicolor (yellow and purple) images should be 48px wide + and the left and right bicolor (orange and blue) + images should be 48px tall. + So, in this first-subtest, the 4 side border-image is + rescaled down, from sliced 64px to 48px. + */ + } + + div#second-subtest + { + height: 80px; + width: 80px; + /* + 80px divided by 64px == 1.25 which is rounded down to 1. + So, each image should be 80px divided by 1 == 80px + wide or tall. That means that the top and bottom + bicolor (yellow and purple) images should be 80px wide + and the left and right bicolor (orange and blue) + images should be 80px tall. + So, in this second-subtest, the 4 side border-image are + rescaled up, from sliced 64px to 80px. + */ + } + + div#third-subtest + { + margin-top: 1em; + + height: 144px; + width: 168px; + /* + 168px divided by 64px == 2.625 which is rounded up to 3. + So, each image should be 168px divided by 3 == 56px + wide. That means that the top and bottom + bicolor (yellow and purple) images should be 56px wide. + 144px divided by 64px == 2.25 which is rounded down to 2. + So, each image should be 144px divided by 2 == 72px + tall. That means that the left and right + bicolor (orange and blue) images should be 72px tall. + So, in this third-subtest, the 2 top and bottom sides + border-image are rescaled down, from sliced 64px to + 56px while the 2 left and right sides border-image are + rescaled up, from sliced 64px to 72px. + */ + } + </style> + + <div id="first-subtest"></div> + + <div id="second-subtest"></div><br> + + <div id="third-subtest"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-003.html.ini b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-003.html.ini new file mode 100644 index 0000000..d00f5a59 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-003.html.ini
@@ -0,0 +1,4 @@ +[border-image-repeat-round-003.html] + expected: + if debug: PASS + FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-stretch-001.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-stretch-001.html new file mode 100644 index 0000000..2a23e78 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-stretch-001.html
@@ -0,0 +1,94 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Backgrounds Test: border-image-repeat: round stretch</title> + + <!-- + + Created: June 23rd 2023 + + Last modified: June 23rd 2023 + + --> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + <link rel="help" href="https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat"> + <link rel="help" href="https://www.w3.org/TR/css-backgrounds-3/#border-image-process"> + <link rel="match" href="reference/border-image-repeat-round-stretch-001-ref.html"> + + <meta content="" name="flags"> + <meta content="This test checks that the process of repeating the tile when 'border-image-repeat' is 'round stretch'. Thanks to a specially crafted rectangular border-image, we check how the sliced border-image is, in the first-subtest, rescaled down horizontally (top and bottom sides), and is, in the second-subtest, rescaled up horizontally (top and bottom sides). In both subtests, the border-image in the vertical axis (left and right sides) are stretched." name="assert"> + + <!-- + + 'round' + The image is tiled (repeated) to fill the area. If it does not + fill the area with a whole number of tiles, the image is rescaled + so that it does. + + https://www.w3.org/TR/css-backgrounds-3/#valdef-border-image-repeat-round + + + 'stretch' + The image is stretched to fill the area. + + https://www.w3.org/TR/css-backgrounds-3/#valdef-border-image-repeat-stretch + + --> + + <style> + div + { + border: red solid 64px; + border-image-repeat: round stretch; + /* + " + The first keyword applies to the horizontal scaling and + tiling of the top, middle and bottom parts, the second to + the vertical scaling and tiling of the left, middle and right parts + " + https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat + */ + border-image-slice: 64 fill; /* the center will be black */ + border-image-source: url("support/4bicolor-squares.png"); + display: inline-block; + margin-right: 1em; + } + + div#first-subtest + { + height: 192px; + width: 96px; + /* + 96px divided by 64px == 1.5 which is rounded up to 2. + So, the image should be 96px divided by 2 == 48px + wide. That means that the top and bottom bicolor + (yellow and purple) images should be 48px wide, + therefore rescaled down, from sliced 64px to 48px. + + The left and right sides should be stretched from + sliced 64px to 192px (3 times). + */ + } + + div#second-subtest + { + height: 128px; + width: 80px; + /* + 80px divided by 64px == 1.25 which is rounded down to 1. + So, each image should be 80px divided by 1 == 80px + wide. That means that the top and bottom bicolor + (yellow and purple) images should be 80px wide + therefore rescaled up, from sliced 64px to 80px. + + The left and right sides should be stretched from + sliced 64px to 128px (2 times). + */ + } + </style> + + <div id="first-subtest"></div> + + <div id="second-subtest"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-stretch-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-stretch-001.html.ini new file mode 100644 index 0000000..37e4faeb --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-round-stretch-001.html.ini
@@ -0,0 +1,4 @@ +[border-image-repeat-round-stretch-001.html] + expected: + if debug: PASS + FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-stretch-round-001.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-stretch-round-001.html new file mode 100644 index 0000000..2ac51fe --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-stretch-round-001.html
@@ -0,0 +1,94 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Backgrounds Test: border-image-repeat: stretch round</title> + + <!-- + + Created: June 23rd 2023 + + Last modified: June 23rd 2023 + + --> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + <link rel="help" href="https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat"> + <link rel="help" href="https://www.w3.org/TR/css-backgrounds-3/#border-image-process"> + <link rel="match" href="reference/border-image-repeat-stretch-round-001-ref.html"> + + <meta content="" name="flags"> + <meta content="This test checks that the process of repeating the tile when 'border-image-repeat' is 'stretch round'. Thanks to a specially crafted rectangular border-image, we check how the sliced border-image is, in the first-subtest, rescaled down vertically (left and right sides), and is, in the second-subtest, rescaled up vertically (left and right sides). In both subtests, the border-image in the horizontal axis (top and bottom sides) are stretched." name="assert"> + + <!-- + + 'stretch' + The image is stretched to fill the area. + + https://www.w3.org/TR/css-backgrounds-3/#valdef-border-image-repeat-stretch + + + 'round' + The image is tiled (repeated) to fill the area. If it does not + fill the area with a whole number of tiles, the image is rescaled + so that it does. + + https://www.w3.org/TR/css-backgrounds-3/#valdef-border-image-repeat-round + + --> + + <style> + div + { + border: red solid 64px; + border-image-repeat: stretch round; + /* + " + The first keyword applies to the horizontal scaling and + tiling of the top, middle and bottom parts, the second to + the vertical scaling and tiling of the left, middle and right parts + " + https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat + */ + border-image-slice: 64 fill; /* the center will be black */ + border-image-source: url("support/4bicolor-squares.png"); + display: inline-block; + margin-right: 1em; + } + + div#first-subtest + { + height: 96px; + width: 192px; + /* + 96px divided by 64px == 1.5 which is rounded up to 2. + So, the image should be 96px divided by 2 == 48px + wide. That means that the left and right + bicolor (orange and blue) images should be 48px tall, + therefore rescaled down, from sliced 64px to 48px. + + The top and bottom sides should be stretched from + sliced 64px to 192px (3 times). + */ + } + + div#second-subtest + { + height: 80px; + width: 128px; + /* + 80px divided by 64px == 1.25 which is rounded down to 1. + So, each image should be 80px divided by 1 == 80px + tall. That means that the left and right bicolor + (orange and blue) images should be 80px tall + therefore rescaled up, from sliced 64px to 80px. + + The top and bottom sides should be stretched from + sliced 64px to 128px (2 times). + */ + } + </style> + + <div id="first-subtest"></div> + + <div id="second-subtest"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-stretch-round-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-stretch-round-001.html.ini new file mode 100644 index 0000000..08a3c26 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-repeat-stretch-round-001.html.ini
@@ -0,0 +1,4 @@ +[border-image-repeat-stretch-round-001.html] + expected: + if debug: PASS + FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-slice-006.htm b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-slice-006.htm index d6484b0..45feaf6 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-slice-006.htm +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-slice-006.htm Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-001.htm b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-001.htm index a07426a6..2a5d02e 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-001.htm +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-001.htm Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-002.htm b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-002.htm deleted file mode 100644 index 3503e837..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-002.htm +++ /dev/null Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-003.htm b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-003.htm deleted file mode 100644 index d85c635..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-003.htm +++ /dev/null Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-004.htm b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-004.htm deleted file mode 100644 index 0d1fc02f..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-image-width-004.htm +++ /dev/null Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/box-shadow-outset-without-border-radius-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/box-shadow-outset-without-border-radius-001.html.ini new file mode 100644 index 0000000..c8a3184 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/box-shadow-outset-without-border-radius-001.html.ini
@@ -0,0 +1,3 @@ +[box-shadow-outset-without-border-radius-001.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-013-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-013-ref.html new file mode 100644 index 0000000..789afe1 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-013-ref.html
@@ -0,0 +1,88 @@ +<!DOCTYPE html> + + <meta charset="utf-8"> + + <title>CSS Reference Test</title> + + <!-- + + Author is Chris Nardi + + --> + + <style> + .borderContainer + { + height: 50px; + position: relative; + width: 50px; + } + + .borderContainer > div + { + background-image: url("../support/green-dot.png"); + height: 10px; + position: absolute; + width: 10px; + } + + .left + { + left: 0px; + } + + .left1 + { + left: 10px; + } + + .left2 + { + left: 20px; + } + + .left3 + { + left: 30px; + } + + .right + { + right: 0px; + } + + .top + { + top: 0px; + } + + .top1 + { + top: 10px; + } + + .top2 + { + top: 20px; + } + + .top3 + { + top: 30px; + } + + .bottom + { + bottom: 0px; + } + </style> + + <p>Pass if the square has borders made of green dots and <strong>no red</strong>. + + <div class="borderContainer"> + <div class="top left"></div><div class="top left1"></div><div class="top left2"></div><div class="top left3"></div><div class="top right"></div> + <div class="left top1"></div><div class="right top1"></div> + <div class="left top2"></div><div class="right top2"></div> + <div class="left top3"></div><div class="right top3"></div> + <div class="bottom left"></div><div class="bottom left1"></div><div class="bottom left2"></div><div class="bottom left3"></div><div class="bottom right"></div> + </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-repeat-round-003-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-repeat-round-003-ref.html new file mode 100644 index 0000000..4b63a4c --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-repeat-round-003-ref.html
@@ -0,0 +1,83 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Reftest reference</title> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + + <style> + div#first-subtest + { + background-color: black; + display: inline-table; + margin-right: 1em; + table-layout: fixed; + + height: 224px; + width: 224px; + } + + div#second-subtest + { + background-color: black; + display: inline-table; + table-layout: fixed; + + height: 208px; + width: 208px; + } + + div#third-subtest + { + background-color: black; + display: inline-table; + margin-top: 1em; + table-layout: fixed; + + height: 272px; + width: 296px; + } + </style> + + <div id="first-subtest"> + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 25%, purple 25% 50%, yellow 50% 75%, purple 75% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; background-image: linear-gradient(orange 0% 25%, blue 25% 50%, orange 50% 75%, blue 75% 100%);"></div><div style="display: table-cell;"></div><div style="display: table-cell; background-image: linear-gradient(orange 0% 25%, blue 25% 50%, orange 50% 75%, blue 75% 100%);"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 25%, purple 25% 50%, yellow 50% 75%, purple 75% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + </div> + + <div id="second-subtest" style="vertical-align: bottom;"> + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 50%, purple 50% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; background-image: linear-gradient(orange 0% 50%, blue 50% 100%);"></div><div style="display: table-cell;"></div><div style="display: table-cell; background-image: linear-gradient(orange 0% 50%, blue 50% 100%);"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 50%, purple 50% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + </div><br> + + <div id="third-subtest"> + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0px 28px, purple 28px 56px, yellow 56px 84px, purple 84px 112px, yellow 112px 140px, purple 140px 168px, yellow 168px 196px, purple 196px 224px);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; background-image: linear-gradient(orange 0% 25%, blue 25% 50%, orange 50% 75%, blue 75% 100%);"></div><div style="display: table-cell;"></div><div style="display: table-cell; background-image: linear-gradient(orange 0% 25%, blue 25% 50%, orange 50% 75%, blue 75% 100%);"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0px 28px, purple 28px 56px, yellow 56px 84px, purple 84px 112px, yellow 112px 140px, purple 140px 168px, yellow 168px 196px, purple 196px 224px);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-repeat-round-stretch-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-repeat-round-stretch-001-ref.html new file mode 100644 index 0000000..3225375 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-repeat-round-stretch-001-ref.html
@@ -0,0 +1,58 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Reftest reference</title> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + + <style> + div#first-subtest + { + background-color: black; + display: inline-table; + margin-right: 1em; + table-layout: fixed; + + height: 320px; + width: 224px; + } + + div#second-subtest + { + background-color: black; + display: inline-table; + table-layout: fixed; + + height: 256px; + width: 208px; + } + </style> + + <div id="first-subtest"> + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 25%, purple 25% 50%, yellow 50% 75%, purple 75% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; background-image: linear-gradient(orange 0% 50%, blue 50% 100%);"></div><div style="display: table-cell;"></div><div style="display: table-cell; background-image: linear-gradient(orange 0% 50%, blue 50% 100%);"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 25%, purple 25% 50%, yellow 50% 75%, purple 75% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + </div> + + <div id="second-subtest" style="vertical-align: bottom;"> + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 50%, purple 50% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; background-image: linear-gradient(orange 0% 50%, blue 50% 100%);"></div><div style="display: table-cell;"></div><div style="display: table-cell; background-image: linear-gradient(orange 0% 50%, blue 50% 100%);"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 50%, purple 50% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-repeat-stretch-round-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-repeat-stretch-round-001-ref.html new file mode 100644 index 0000000..931dd3b --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-repeat-stretch-round-001-ref.html
@@ -0,0 +1,58 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Reftest reference</title> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + + <style> + div#first-subtest + { + background-color: black; + display: inline-table; + margin-right: 1em; + table-layout: fixed; + + height: 224px; + width: 320px; + } + + div#second-subtest + { + background-color: black; + display: inline-table; + table-layout: fixed; + + height: 208px; + width: 256px; + } + </style> + + <div id="first-subtest"> + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 50%, purple 50% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; background-image: linear-gradient(orange 0% 25%, blue 25% 50%, orange 50% 75%, blue 75% 100%);"></div><div style="display: table-cell;"></div><div style="display: table-cell; background-image: linear-gradient(orange 0% 25%, blue 25% 50%, orange 50% 75%, blue 75% 100%);"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 50%, purple 50% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + </div> + + <div id="second-subtest" style="vertical-align: bottom;"> + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 50%, purple 50% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; background-image: linear-gradient(orange 0% 50%, blue 50% 100%);"></div><div style="display: table-cell;"></div><div style="display: table-cell; background-image: linear-gradient(orange 0% 50%, blue 50% 100%);"></div> + </div> + + <div style="display: table-row;"> + <div style="display: table-cell; height: 64px; width: 64px;"></div><div style="display: table-cell; background-image: linear-gradient(to right, yellow 0% 50%, purple 50% 100%);"></div><div style="display: table-cell; height: 64px; width: 64px;"></div> + </div> + </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-slice-006-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-slice-006-ref.html new file mode 100644 index 0000000..d431267 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-slice-006-ref.html
@@ -0,0 +1,37 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Reftest reference</title> + + <style> + div + { + background-color: green; + height: 30px; + margin: 50px; + position: relative; + width: 30px; + } + + div#top-right + { + left: 130px; + bottom: 80px; + } + + div#bottom-left + { + bottom: 30px; + } + + div#bottom-right + { + left: 130px; + bottom: 110px; + } + </style> + + <p>Test passes if there are four identical green squares and <strong>no red</strong>. + + <div id="top-left"></div><div id="top-right"></div><div id="bottom-left"></div><div id="bottom-right"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-width-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-width-001-ref.html new file mode 100644 index 0000000..a3e1a91 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-image-width-001-ref.html
@@ -0,0 +1,61 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Reftest reference</title> + + <style> + div.subtest + { + background-color: green; + height: 120px; + margin: 8px auto 8px 0px; + width: 120px; + } + + div > div + { + background-color: lime; + position: relative; + } + + div#subtest1 > div + { + height: 75px; + left: 15px; + top: 30px; + width: 80px; + } + + div#subtest2 > div + { + height: 60px; + left: 10px; + top: 30px; + width: 100px; + } + + div#subtest3 > div + { + height: 75px; + left: 15px; + top: 30px; + width: 80px; + } + + div#subtest4 > div + { + height: 90px; + left: 25px; + top: 15px; + width: 70px; + } + </style> + + <div class="subtest" id="subtest1"><div></div></div> + + <div class="subtest" id="subtest2"><div></div></div> + + <div class="subtest" id="subtest3" style="height: 135px; width: 110px;"><div></div></div> + + <div class="subtest" id="subtest4"><div></div></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/support/4bicolor-squares.png b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/support/4bicolor-squares.png new file mode 100644 index 0000000..252710d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/support/4bicolor-squares.png Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/support/green-dot.png b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/support/green-dot.png new file mode 100644 index 0000000..034353e --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/support/green-dot.png Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/hsl-007.html.ini b/third_party/blink/web_tests/external/wpt/css/css-color/hsl-007.html.ini new file mode 100644 index 0000000..e57393b5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-color/hsl-007.html.ini
@@ -0,0 +1,3 @@ +[hsl-007.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/hsla-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-color/hsla-001.html.ini new file mode 100644 index 0000000..d8ec9bd --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-color/hsla-001.html.ini
@@ -0,0 +1,3 @@ +[hsla-001.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-select-elem-003.html.ini b/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-select-elem-003.html.ini index 8146241..366687b 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-select-elem-003.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-select-elem-003.html.ini
@@ -1,4 +1,4 @@ [contain-size-select-elem-003.html] expected: - if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL + if (product == "content_shell") and (os == "win"): FAIL if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/canvas-as-container-003.html.ini b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/canvas-as-container-003.html.ini index 66bfbf0e..52ae4b5 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/canvas-as-container-003.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/canvas-as-container-003.html.ini
@@ -1,6 +1,6 @@ [canvas-as-container-003.html] expected: - if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL - if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL - if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL - if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL + if (product == "content_shell") and (os == "linux"): PASS + if (product == "content_shell") and (os == "win"): PASS + if product == "chrome": PASS + FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-display/display-contents-dynamic-generated-content-fieldset-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-display/display-contents-dynamic-generated-content-fieldset-001.html.ini index a7c697a6..e4253ef 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-display/display-contents-dynamic-generated-content-fieldset-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-display/display-contents-dynamic-generated-content-fieldset-001.html.ini
@@ -2,3 +2,4 @@ expected: if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL if (product == "content_shell") and (os == "win"): FAIL + if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/align-content-001.htm.ini b/third_party/blink/web_tests/external/wpt/css/css-flexbox/align-content-001.htm.ini new file mode 100644 index 0000000..57815c6 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/align-content-001.htm.ini
@@ -0,0 +1,3 @@ +[align-content-001.htm] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/align-items-002.htm.ini b/third_party/blink/web_tests/external/wpt/css/css-flexbox/align-items-002.htm.ini new file mode 100644 index 0000000..af70703 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/align-items-002.htm.ini
@@ -0,0 +1,3 @@ +[align-items-002.htm] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/css-flexbox-row.html.ini b/third_party/blink/web_tests/external/wpt/css/css-flexbox/css-flexbox-row.html.ini index 1383811..8d6de9d 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-flexbox/css-flexbox-row.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/css-flexbox-row.html.ini
@@ -1,3 +1,4 @@ [css-flexbox-row.html] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-flow-004.html.ini b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-flow-004.html.ini new file mode 100644 index 0000000..ca6c4150 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-flow-004.html.ini
@@ -0,0 +1,3 @@ +[flex-flow-004.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox-overflow-horiz-004.html.ini b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox-overflow-horiz-004.html.ini new file mode 100644 index 0000000..529a1a5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox-overflow-horiz-004.html.ini
@@ -0,0 +1,3 @@ +[flexbox-overflow-horiz-004.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox-with-pseudo-elements-002.html.ini b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox-with-pseudo-elements-002.html.ini new file mode 100644 index 0000000..7fd990d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox-with-pseudo-elements-002.html.ini
@@ -0,0 +1,3 @@ +[flexbox-with-pseudo-elements-002.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_flex-initial-2.html.ini b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_flex-initial-2.html.ini index 7ca1282..b43c30e5 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_flex-initial-2.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_flex-initial-2.html.ini
@@ -1,3 +1,4 @@ [flexbox_flex-initial-2.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/table-with-float-paint.html.ini b/third_party/blink/web_tests/external/wpt/css/css-flexbox/table-with-float-paint.html.ini new file mode 100644 index 0000000..61fc73b --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/table-with-float-paint.html.ini
@@ -0,0 +1,3 @@ +[table-with-float-paint.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-022.xht.ini b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-022.xht.ini new file mode 100644 index 0000000..70f063c0 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-022.xht.ini
@@ -0,0 +1,3 @@ +[font-family-name-022.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-33.html.ini b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-33.html.ini index cc813c20..39c5b22d 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-33.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-33.html.ini
@@ -1,3 +1,4 @@ [font-palette-33.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-item-aspect-ratio-stretch-3.html.ini b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-item-aspect-ratio-stretch-3.html.ini index 0d5234e..01ae1c1 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-item-aspect-ratio-stretch-3.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-item-aspect-ratio-stretch-3.html.ini
@@ -1,4 +1,5 @@ [grid-item-aspect-ratio-stretch-3.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/table-grid-item-dynamic-004.html.ini b/third_party/blink/web_tests/external/wpt/css/css-grid/table-grid-item-dynamic-004.html.ini new file mode 100644 index 0000000..c0ee0548 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-grid/table-grid-item-dynamic-004.html.ini
@@ -0,0 +1,3 @@ +[table-grid-item-dynamic-004.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-001.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-001.tentative-expected.txt deleted file mode 100644 index d6abbcc7..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-001.tentative-expected.txt +++ /dev/null
@@ -1,39 +0,0 @@ -This is a testharness.js-based test. -FAIL .target > * 1 assert_equals: -<span data-offset-y="33"></span> -offsetTop expected 33 but got 29 -FAIL .target > * 2 assert_equals: -<textarea data-offset-y="11" class="inner"></textarea> -offsetTop expected 11 but got 10 -FAIL .target > * 3 assert_equals: -<span data-offset-y="33"></span> -offsetTop expected 33 but got 29 -FAIL .target > * 4 assert_equals: -<textarea data-offset-y="11" class="inner">X X X X X</textarea> -offsetTop expected 11 but got 10 -FAIL .target > * 5 assert_equals: -<span data-offset-y="33"></span> -offsetTop expected 33 but got 29 -FAIL .target > * 6 assert_equals: -<textarea data-offset-y="11" class="inner" placeholder="X X X X X"></textarea> -offsetTop expected 11 but got 10 -FAIL .target > * 7 assert_equals: -<span data-offset-y="81"></span> -offsetTop expected 81 but got 80 -FAIL .target > * 8 assert_equals: -<textarea data-offset-y="11" class="inner" style="font-size: 100px;"></textarea> -offsetTop expected 11 but got 10 -FAIL .target > * 9 assert_equals: -<span data-offset-y="81"></span> -offsetTop expected 81 but got 80 -FAIL .target > * 10 assert_equals: -<textarea data-offset-y="11" class="inner" style="font-size: 100px;">X X X X X</textarea> -offsetTop expected 11 but got 10 -FAIL .target > * 11 assert_equals: -<span data-offset-y="81"></span> -offsetTop expected 81 but got 80 -FAIL .target > * 12 assert_equals: -<textarea data-offset-y="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> -offsetTop expected 11 but got 10 -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-001.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-001.tentative.html index ccae10c..02b2051c 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-001.tentative.html +++ b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-001.tentative.html
@@ -35,6 +35,9 @@ height: 60px; vertical-align: baseline; font-family: Ahem; + line-height: 1; + margin: 0; + padding: 0; } </style> @@ -43,26 +46,26 @@ <script src="/resources/check-layout-th.js"></script> <body onload="checkLayout('.target > *')"> <div class="target"> - <span data-offset-y="33"></span> - <textarea data-offset-y="11" class="inner"></textarea> + <span data-offset-y="29"></span> + <textarea data-offset-y="10" class="inner"></textarea> </div> <div class="target"> - <span data-offset-y="33"></span> - <textarea data-offset-y="11" class="inner">X X X X X</textarea> + <span data-offset-y="29"></span> + <textarea data-offset-y="10" class="inner">X X X X X</textarea> </div> <div class="target"> - <span data-offset-y="33"></span> - <textarea data-offset-y="11" class="inner" placeholder="X X X X X"></textarea> + <span data-offset-y="29"></span> + <textarea data-offset-y="10" class="inner" placeholder="X X X X X"></textarea> </div> <div class="target"> - <span data-offset-y="81"></span> - <textarea data-offset-y="11" class="inner" style="font-size: 100px;"></textarea> + <span data-offset-y="80"></span> + <textarea data-offset-y="10" class="inner" style="font-size: 100px;"></textarea> </div> <div class="target"> - <span data-offset-y="81"></span> - <textarea data-offset-y="11" class="inner" style="font-size: 100px;">X X X X X</textarea> + <span data-offset-y="80"></span> + <textarea data-offset-y="10" class="inner" style="font-size: 100px;">X X X X X</textarea> </div> <div class="target"> - <span data-offset-y="81"></span> - <textarea data-offset-y="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> + <span data-offset-y="80"></span> + <textarea data-offset-y="10" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-002.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-002.tentative-expected.txt index 7fa1abf5..1d3ebd14 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-002.tentative-expected.txt +++ b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-002.tentative-expected.txt
@@ -1,39 +1,19 @@ This is a testharness.js-based test. -FAIL .target > * 1 assert_equals: -<span data-offset-x="61"></span> -offsetLeft expected 61 but got 60 -FAIL .target > * 2 assert_equals: -<textarea data-offset-x="11" class="inner"></textarea> -offsetLeft expected 11 but got 10 -FAIL .target > * 3 assert_equals: -<span data-offset-x="61"></span> -offsetLeft expected 61 but got 60 -FAIL .target > * 4 assert_equals: -<textarea data-offset-x="11" class="inner">X X X X X</textarea> -offsetLeft expected 11 but got 10 -FAIL .target > * 5 assert_equals: -<span data-offset-x="61"></span> -offsetLeft expected 61 but got 60 -FAIL .target > * 6 assert_equals: -<textarea data-offset-x="11" class="inner" placeholder="X X X X X"></textarea> -offsetLeft expected 11 but got 10 -FAIL .target > * 7 assert_equals: -<span data-offset-x="26"></span> -offsetLeft expected 26 but got 25 -FAIL .target > * 8 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;"></textarea> -offsetLeft expected 11 but got 10 +PASS .target > * 1 +PASS .target > * 2 +PASS .target > * 3 +PASS .target > * 4 +PASS .target > * 5 +PASS .target > * 6 +PASS .target > * 7 +PASS .target > * 8 FAIL .target > * 9 assert_equals: -<span data-offset-x="26"></span> -offsetLeft expected 26 but got 10 -FAIL .target > * 10 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;">X X X X X</textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="25"></span> +offsetLeft expected 25 but got 10 +PASS .target > * 10 FAIL .target > * 11 assert_equals: -<span data-offset-x="26"></span> -offsetLeft expected 26 but got 10 -FAIL .target > * 12 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="25"></span> +offsetLeft expected 25 but got 10 +PASS .target > * 12 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-002.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-002.tentative.html index 6c2b0a2..633ba61 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-002.tentative.html +++ b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-002.tentative.html
@@ -37,6 +37,8 @@ vertical-align: baseline; font-family: Ahem; line-height: 1; + margin: 0; + padding: 0; } </style> @@ -45,26 +47,26 @@ <script src="/resources/check-layout-th.js"></script> <body onload="checkLayout('.target > *')"> <div class="target"> - <span data-offset-x="61"></span> - <textarea data-offset-x="11" class="inner"></textarea> + <span data-offset-x="60"></span> + <textarea data-offset-x="10" class="inner"></textarea> </div> <div class="target"> - <span data-offset-x="61"></span> - <textarea data-offset-x="11" class="inner">X X X X X</textarea> + <span data-offset-x="60"></span> + <textarea data-offset-x="10" class="inner">X X X X X</textarea> </div> <div class="target"> - <span data-offset-x="61"></span> - <textarea data-offset-x="11" class="inner" placeholder="X X X X X"></textarea> + <span data-offset-x="60"></span> + <textarea data-offset-x="10" class="inner" placeholder="X X X X X"></textarea> </div> <div class="target"> - <span data-offset-x="26"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;"></textarea> + <span data-offset-x="25"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;"></textarea> </div> <div class="target"> - <span data-offset-x="26"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;">X X X X X</textarea> + <span data-offset-x="25"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;">X X X X X</textarea> </div> <div class="target"> - <span data-offset-x="26"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> + <span data-offset-x="25"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-003.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-003.tentative-expected.txt deleted file mode 100644 index 677e0c6..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-003.tentative-expected.txt +++ /dev/null
@@ -1,39 +0,0 @@ -This is a testharness.js-based test. -FAIL .target > * 1 assert_equals: -<span data-offset-x="31"></span> -offsetLeft expected 31 but got 30 -FAIL .target > * 2 assert_equals: -<textarea data-offset-x="11" class="inner"></textarea> -offsetLeft expected 11 but got 10 -FAIL .target > * 3 assert_equals: -<span data-offset-x="31"></span> -offsetLeft expected 31 but got 30 -FAIL .target > * 4 assert_equals: -<textarea data-offset-x="11" class="inner">X X X X X</textarea> -offsetLeft expected 11 but got 10 -FAIL .target > * 5 assert_equals: -<span data-offset-x="31"></span> -offsetLeft expected 31 but got 30 -FAIL .target > * 6 assert_equals: -<textarea data-offset-x="11" class="inner" placeholder="X X X X X"></textarea> -offsetLeft expected 11 but got 10 -FAIL .target > * 7 assert_equals: -<span data-offset-x="66"></span> -offsetLeft expected 66 but got 65 -FAIL .target > * 8 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;"></textarea> -offsetLeft expected 11 but got 10 -FAIL .target > * 9 assert_equals: -<span data-offset-x="66"></span> -offsetLeft expected 66 but got 65 -FAIL .target > * 10 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;">X X X X X</textarea> -offsetLeft expected 11 but got 10 -FAIL .target > * 11 assert_equals: -<span data-offset-x="66"></span> -offsetLeft expected 66 but got 65 -FAIL .target > * 12 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> -offsetLeft expected 11 but got 10 -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-003.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-003.tentative.html index 0de72e8..098b05bd 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-003.tentative.html +++ b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-first-textarea-003.tentative.html
@@ -37,6 +37,8 @@ vertical-align: baseline; font-family: Ahem; line-height: 1; + margin: 0; + padding: 0; } </style> @@ -45,26 +47,26 @@ <script src="/resources/check-layout-th.js"></script> <body onload="checkLayout('.target > *')"> <div class="target"> - <span data-offset-x="31"></span> - <textarea data-offset-x="11" class="inner"></textarea> + <span data-offset-x="30"></span> + <textarea data-offset-x="10" class="inner"></textarea> </div> <div class="target"> - <span data-offset-x="31"></span> - <textarea data-offset-x="11" class="inner">X X X X X</textarea> + <span data-offset-x="30"></span> + <textarea data-offset-x="10" class="inner">X X X X X</textarea> </div> <div class="target"> - <span data-offset-x="31"></span> - <textarea data-offset-x="11" class="inner" placeholder="X X X X X"></textarea> + <span data-offset-x="30"></span> + <textarea data-offset-x="10" class="inner" placeholder="X X X X X"></textarea> </div> <div class="target"> - <span data-offset-x="66"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;"></textarea> + <span data-offset-x="65"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;"></textarea> </div> <div class="target"> - <span data-offset-x="66"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;">X X X X X</textarea> + <span data-offset-x="65"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;">X X X X X</textarea> </div> <div class="target"> - <span data-offset-x="66"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> + <span data-offset-x="65"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-001.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-001.tentative-expected.txt index aa33cde..0056956f 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-001.tentative-expected.txt +++ b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-001.tentative-expected.txt
@@ -1,39 +1,19 @@ This is a testharness.js-based test. FAIL .target > * 1 assert_equals: -<span data-offset-y="81"></span> -offsetTop expected 81 but got 29 -FAIL .target > * 2 assert_equals: -<textarea data-offset-y="11" class="inner"></textarea> -offsetTop expected 11 but got 10 -FAIL .target > * 3 assert_equals: -<span data-offset-y="81"></span> -offsetTop expected 81 but got 80 -FAIL .target > * 4 assert_equals: -<textarea data-offset-y="11" class="inner">X X X X X</textarea> -offsetTop expected 11 but got 10 +<span data-offset-y="80"></span> +offsetTop expected 80 but got 29 +PASS .target > * 2 +PASS .target > * 3 +PASS .target > * 4 FAIL .target > * 5 assert_equals: -<span data-offset-y="81"></span> -offsetTop expected 81 but got 29 -FAIL .target > * 6 assert_equals: -<textarea data-offset-y="11" class="inner" placeholder="X X X X X"></textarea> -offsetTop expected 11 but got 10 -FAIL .target > * 7 assert_equals: -<span data-offset-y="81"></span> -offsetTop expected 81 but got 80 -FAIL .target > * 8 assert_equals: -<textarea data-offset-y="11" class="inner" style="font-size: 100px;"></textarea> -offsetTop expected 11 but got 10 -FAIL .target > * 9 assert_equals: -<span data-offset-y="81"></span> -offsetTop expected 81 but got 80 -FAIL .target > * 10 assert_equals: -<textarea data-offset-y="11" class="inner" style="font-size: 100px;">X X X X X</textarea> -offsetTop expected 11 but got 10 -FAIL .target > * 11 assert_equals: -<span data-offset-y="81"></span> -offsetTop expected 81 but got 80 -FAIL .target > * 12 assert_equals: -<textarea data-offset-y="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> -offsetTop expected 11 but got 10 +<span data-offset-y="80"></span> +offsetTop expected 80 but got 29 +PASS .target > * 6 +PASS .target > * 7 +PASS .target > * 8 +PASS .target > * 9 +PASS .target > * 10 +PASS .target > * 11 +PASS .target > * 12 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-001.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-001.tentative.html index c6e45a4..156c007 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-001.tentative.html +++ b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-001.tentative.html
@@ -35,6 +35,9 @@ height: 60px; vertical-align: baseline; font-family: Ahem; + line-height: 1; + margin: 0; + padding: 0; } </style> @@ -43,27 +46,27 @@ <script src="/resources/check-layout-th.js"></script> <body onload="checkLayout('.target > *')"> <div class="target"> - <span data-offset-y="81"></span> - <textarea data-offset-y="11" class="inner"></textarea> + <span data-offset-y="80"></span> + <textarea data-offset-y="10" class="inner"></textarea> </div> <div class="target"> - <span data-offset-y="81"></span> - <textarea data-offset-y="11" class="inner">X X X X X</textarea> + <span data-offset-y="80"></span> + <textarea data-offset-y="10" class="inner">X X X X X</textarea> </div> <div class="target"> - <span data-offset-y="81"></span> - <textarea data-offset-y="11" class="inner" placeholder="X X X X X"></textarea> + <span data-offset-y="80"></span> + <textarea data-offset-y="10" class="inner" placeholder="X X X X X"></textarea> </div> <div class="target"> - <span data-offset-y="81"></span> - <textarea data-offset-y="11" class="inner" style="font-size: 100px;"></textarea> + <span data-offset-y="80"></span> + <textarea data-offset-y="10" class="inner" style="font-size: 100px;"></textarea> </div> <div class="target"> - <span data-offset-y="81"></span> - <textarea data-offset-y="11" class="inner" style="font-size: 100px;">X X X X X</textarea> + <span data-offset-y="80"></span> + <textarea data-offset-y="10" class="inner" style="font-size: 100px;">X X X X X</textarea> </div> <div class="target"> - <span data-offset-y="81"></span> - <textarea data-offset-y="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> + <span data-offset-y="80"></span> + <textarea data-offset-y="10" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-002.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-002.tentative-expected.txt index 051e34ec..31b3070 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-002.tentative-expected.txt +++ b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-002.tentative-expected.txt
@@ -1,39 +1,31 @@ This is a testharness.js-based test. FAIL .target > * 1 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 60 -FAIL .target > * 2 assert_equals: -<textarea data-offset-x="11" class="inner"></textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 60 +PASS .target > * 2 FAIL .target > * 3 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 10 FAIL .target > * 4 assert_equals: -<textarea data-offset-x="11" class="inner">X X X X X</textarea> -offsetLeft expected 11 but got 20 +<textarea data-offset-x="10" class="inner">X X X X X</textarea> +offsetLeft expected 10 but got 20 FAIL .target > * 5 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 60 -FAIL .target > * 6 assert_equals: -<textarea data-offset-x="11" class="inner" placeholder="X X X X X"></textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 60 +PASS .target > * 6 FAIL .target > * 7 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 25 -FAIL .target > * 8 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;"></textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 25 +PASS .target > * 8 FAIL .target > * 9 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 10 FAIL .target > * 10 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;">X X X X X</textarea> -offsetLeft expected 11 but got 20 +<textarea data-offset-x="10" class="inner" style="font-size: 100px;">X X X X X</textarea> +offsetLeft expected 10 but got 20 FAIL .target > * 11 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 10 -FAIL .target > * 12 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 10 +PASS .target > * 12 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-002.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-002.tentative.html index 6bba600..d8b20b3 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-002.tentative.html +++ b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-002.tentative.html
@@ -37,6 +37,8 @@ vertical-align: baseline; font-family: Ahem; line-height: 1; + margin: 0; + padding: 0; } </style> @@ -45,26 +47,26 @@ <script src="/resources/check-layout-th.js"></script> <body onload="checkLayout('.target > *')"> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner"></textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner"></textarea> </div> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner">X X X X X</textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner">X X X X X</textarea> </div> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner" placeholder="X X X X X"></textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner" placeholder="X X X X X"></textarea> </div> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;"></textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;"></textarea> </div> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;">X X X X X</textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;">X X X X X</textarea> </div> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-003.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-003.tentative-expected.txt index 08a9dd8..4eaac8db 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-003.tentative-expected.txt +++ b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-003.tentative-expected.txt
@@ -1,39 +1,27 @@ This is a testharness.js-based test. FAIL .target > * 1 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 30 -FAIL .target > * 2 assert_equals: -<textarea data-offset-x="11" class="inner"></textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 30 +PASS .target > * 2 FAIL .target > * 3 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 90 -FAIL .target > * 4 assert_equals: -<textarea data-offset-x="11" class="inner">X X X X X</textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 90 +PASS .target > * 4 FAIL .target > * 5 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 30 -FAIL .target > * 6 assert_equals: -<textarea data-offset-x="11" class="inner" placeholder="X X X X X"></textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 30 +PASS .target > * 6 FAIL .target > * 7 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 65 -FAIL .target > * 8 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;"></textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 65 +PASS .target > * 8 FAIL .target > * 9 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 90 -FAIL .target > * 10 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;">X X X X X</textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 90 +PASS .target > * 10 FAIL .target > * 11 assert_equals: -<span data-offset-x="46"></span> -offsetLeft expected 46 but got 65 -FAIL .target > * 12 assert_equals: -<textarea data-offset-x="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> -offsetLeft expected 11 but got 10 +<span data-offset-x="45"></span> +offsetLeft expected 45 but got 65 +PASS .target > * 12 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-003.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-003.tentative.html index 45fb2a40..c80d464 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-003.tentative.html +++ b/third_party/blink/web_tests/external/wpt/css/css-inline/baseline-source/baseline-source-last-textarea-003.tentative.html
@@ -37,6 +37,8 @@ vertical-align: baseline; font-family: Ahem; line-height: 1; + margin: 0; + padding: 0; } </style> @@ -45,26 +47,26 @@ <script src="/resources/check-layout-th.js"></script> <body onload="checkLayout('.target > *')"> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner"></textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner"></textarea> </div> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner">X X X X X</textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner">X X X X X</textarea> </div> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner" placeholder="X X X X X"></textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner" placeholder="X X X X X"></textarea> </div> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;"></textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;"></textarea> </div> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;">X X X X X</textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;">X X X X X</textarea> </div> <div class="target"> - <span data-offset-x="46"></span> - <textarea data-offset-x="11" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> + <span data-offset-x="45"></span> + <textarea data-offset-x="10" class="inner" style="font-size: 100px;" placeholder="X X X X X"></textarea> </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-logical/logical-values-float-clear-2.html.ini b/third_party/blink/web_tests/external/wpt/css/css-logical/logical-values-float-clear-2.html.ini index 303554e5..302d9db 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-logical/logical-values-float-clear-2.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-logical/logical-values-float-clear-2.html.ini
@@ -1,3 +1,4 @@ [logical-values-float-clear-2.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-multicol/multicol-width-ch-001.xht.ini b/third_party/blink/web_tests/external/wpt/css/css-multicol/multicol-width-ch-001.xht.ini new file mode 100644 index 0000000..0cdf2fa --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-multicol/multicol-width-ch-001.xht.ini
@@ -0,0 +1,3 @@ +[multicol-width-ch-001.xht] + expected: + if (product == "chrome") and not debug: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving-ref.html b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving-ref.html index 36b07c92..8fb7c86 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving-ref.html +++ b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving-ref.html
@@ -15,4 +15,5 @@ <body> <p>Tests pass if <strong>block is green</strong></p> <div class="test"></div> + <div class="test"></div> </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html index d399142..561b5a3a 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html +++ b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html
@@ -17,6 +17,12 @@ } } + .does-not-exist { + :is(.test-2, :unknown(div,&)) { + background-color: green; + } + } + body * + * { margin-top: 8px; } @@ -24,4 +30,5 @@ <body> <p>Tests pass if <strong>block is green</strong></p> <div class="test test-1"></div> + <div class="test test-2"></div> </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html.ini b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html.ini new file mode 100644 index 0000000..cbc2991 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html.ini
@@ -0,0 +1,4 @@ +[nest-containing-forgiving.html] + expected: + if debug: PASS + FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-visual-box.html.ini b/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-visual-box.html.ini index da6cc9b..9b512744 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-visual-box.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-visual-box.html.ini
@@ -1,3 +1,4 @@ [overflow-clip-margin-visual-box.html] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/overlay/overlay-transition-backdrop.html.ini b/third_party/blink/web_tests/external/wpt/css/css-position/overlay/overlay-transition-backdrop.html.ini index 9a102a9..5cbd39c4 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-position/overlay/overlay-transition-backdrop.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-position/overlay/overlay-transition-backdrop.html.ini
@@ -1,6 +1,5 @@ [overlay-transition-backdrop.html] expected: - if (product == "content_shell") and (os == "mac") and (port == "mac11"): PASS - if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): PASS if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): PASS + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): PASS FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/static-position/vrl-rtl-ltr.tentative.html.ini b/third_party/blink/web_tests/external/wpt/css/css-position/static-position/vrl-rtl-ltr.tentative.html.ini index 1c4e696..28f89c9 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-position/static-position/vrl-rtl-ltr.tentative.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-position/static-position/vrl-rtl-ltr.tentative.html.ini
@@ -1,3 +1,4 @@ [vrl-rtl-ltr.tentative.html] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scoping/slotted-parsing.html b/third_party/blink/web_tests/external/wpt/css/css-scoping/slotted-parsing.html index 25b0030..bed4dedd 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-scoping/slotted-parsing.html +++ b/third_party/blink/web_tests/external/wpt/css/css-scoping/slotted-parsing.html
@@ -28,12 +28,12 @@ test_valid_selector("::slotted(:not(.a))"); test_valid_selector("::slotted(*):is()"); - test_valid_selector("::slotted(*):is(:hover)", "::slotted(*):is()"); - test_valid_selector("::slotted(*):is(#id)", "::slotted(*):is()"); + test_valid_selector("::slotted(*):is(:hover)"); + test_valid_selector("::slotted(*):is(#id)"); test_valid_selector("::slotted(*):where()"); - test_valid_selector("::slotted(*):where(:hover)", "::slotted(*):where()"); - test_valid_selector("::slotted(*):where(#id)", "::slotted(*):where()"); + test_valid_selector("::slotted(*):where(:hover)"); + test_valid_selector("::slotted(*):where(#id)"); // Allow tree-abiding pseudo elements after ::slotted test_valid_selector("::slotted(*)::before");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-009.html.ini b/third_party/blink/web_tests/external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-009.html.ini index 481a42b..cc44220 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-009.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-009.html.ini
@@ -1,3 +1,4 @@ [shape-outside-box-009.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/aspect-ratio/replaced-element-012.html.ini b/third_party/blink/web_tests/external/wpt/css/css-sizing/aspect-ratio/replaced-element-012.html.ini new file mode 100644 index 0000000..ae2e9e5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/aspect-ratio/replaced-element-012.html.ini
@@ -0,0 +1,3 @@ +[replaced-element-012.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/box-sizing-border-box-003.xht.ini b/third_party/blink/web_tests/external/wpt/css/css-sizing/box-sizing-border-box-003.xht.ini new file mode 100644 index 0000000..1f161f5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/box-sizing-border-box-003.xht.ini
@@ -0,0 +1,3 @@ +[box-sizing-border-box-003.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/css3-text-line-break-opclns-031.html.ini b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/css3-text-line-break-opclns-031.html.ini new file mode 100644 index 0000000..e14defe --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/css3-text-line-break-opclns-031.html.ini
@@ -0,0 +1,3 @@ +[css3-text-line-break-opclns-031.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/white-space-pre-032.html.ini b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/white-space-pre-032.html.ini new file mode 100644 index 0000000..64941fc --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/white-space-pre-032.html.ini
@@ -0,0 +1,3 @@ +[white-space-pre-032.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/word-break/word-break-break-all-014.html.ini b/third_party/blink/web_tests/external/wpt/css/css-text/word-break/word-break-break-all-014.html.ini new file mode 100644 index 0000000..115cfea --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-text/word-break/word-break-break-all-014.html.ini
@@ -0,0 +1,3 @@ +[word-break-break-all-014.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/matrix/svg-matrix-065.html.ini b/third_party/blink/web_tests/external/wpt/css/css-transforms/matrix/svg-matrix-065.html.ini new file mode 100644 index 0000000..28db25d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/matrix/svg-matrix-065.html.ini
@@ -0,0 +1,3 @@ +[svg-matrix-065.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/skewX/svg-skewx-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-transforms/skewX/svg-skewx-001.html.ini new file mode 100644 index 0000000..cfeb825b --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/skewX/svg-skewx-001.html.ini
@@ -0,0 +1,3 @@ +[svg-skewx-001.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/box-sizing-005.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/box-sizing-005.html.ini new file mode 100644 index 0000000..bae24324 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/box-sizing-005.html.ini
@@ -0,0 +1,3 @@ +[box-sizing-005.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/box-sizing-007.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/box-sizing-007.html.ini index 5cf40fdd..9464243 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/box-sizing-007.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/box-sizing-007.html.ini
@@ -1,3 +1,4 @@ [box-sizing-007.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-background-size-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-background-size-001.html.ini index b096970..47cade4b 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-background-size-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-background-size-001.html.ini
@@ -1,3 +1,4 @@ [kind-of-widget-fallback-button-background-size-001.html] expected: if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL + if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-bottom-left-radius-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-bottom-left-radius-001.html.ini index 9b4e9fb..84b2ee4 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-bottom-left-radius-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-bottom-left-radius-001.html.ini
@@ -2,3 +2,4 @@ expected: if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL + if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-bottom-style-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-bottom-style-001.html.ini new file mode 100644 index 0000000..fc4344f --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-bottom-style-001.html.ini
@@ -0,0 +1,4 @@ +[kind-of-widget-fallback-button-border-bottom-style-001.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-end-start-radius-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-end-start-radius-001.html.ini index f720f41..fc7094f 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-end-start-radius-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-end-start-radius-001.html.ini
@@ -1,5 +1,6 @@ [kind-of-widget-fallback-button-border-end-start-radius-001.html] expected: if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-image-outset-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-image-outset-001.html.ini index ee4df6d..c9fa79f1 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-image-outset-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-image-outset-001.html.ini
@@ -1,3 +1,6 @@ [kind-of-widget-fallback-button-border-image-outset-001.html] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-inline-end-width-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-inline-end-width-001.html.ini index 191d637e..f4f737d 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-inline-end-width-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-inline-end-width-001.html.ini
@@ -1,3 +1,4 @@ [kind-of-widget-fallback-button-border-inline-end-width-001.html] expected: + if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-left-style-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-left-style-001.html.ini new file mode 100644 index 0000000..0bbe6c6 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-left-style-001.html.ini
@@ -0,0 +1,3 @@ +[kind-of-widget-fallback-button-border-left-style-001.html] + expected: + if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-start-start-radius-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-start-start-radius-001.html.ini new file mode 100644 index 0000000..1e7bb363 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-button-border-start-start-radius-001.html.ini
@@ -0,0 +1,3 @@ +[kind-of-widget-fallback-button-border-start-start-radius-001.html] + expected: + if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-button-border-image-width-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-button-border-image-width-001.html.ini index bdd8aef..263d9b695 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-button-border-image-width-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-button-border-image-width-001.html.ini
@@ -1,4 +1,5 @@ [kind-of-widget-fallback-input-button-border-image-width-001.html] expected: if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-button-border-start-end-radius-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-button-border-start-end-radius-001.html.ini index 0d50a5dcf..85cdb77 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-button-border-start-end-radius-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-button-border-start-end-radius-001.html.ini
@@ -1,3 +1,4 @@ [kind-of-widget-fallback-input-button-border-start-end-radius-001.html] expected: if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL + if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-reset-border-block-end-style-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-reset-border-block-end-style-001.html.ini index 426e200..c454e2c2 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-reset-border-block-end-style-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-reset-border-block-end-style-001.html.ini
@@ -1,3 +1,4 @@ [kind-of-widget-fallback-input-reset-border-block-end-style-001.html] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-end-style-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-end-style-001.html.ini index 2faf179..36eca30 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-end-style-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-end-style-001.html.ini
@@ -1,4 +1,5 @@ [kind-of-widget-fallback-input-search-border-block-end-style-001.html] expected: if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-start-width-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-start-width-001.html.ini index e5fdc27..c23ebe7 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-start-width-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-start-width-001.html.ini
@@ -1,3 +1,4 @@ [kind-of-widget-fallback-input-search-border-block-start-width-001.html] expected: + if (product == "content_shell") and (os == "linux"): FAIL if (product == "content_shell") and (os == "win"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-bottom-color-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-bottom-color-001.html.ini index 52c152f6..ccc02f05 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-bottom-color-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-bottom-color-001.html.ini
@@ -1,4 +1,5 @@ [kind-of-widget-fallback-input-search-border-bottom-color-001.html] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-image-width-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-image-width-001.html.ini index 0a07ac8..806ae473 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-image-width-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-image-width-001.html.ini
@@ -1,3 +1,4 @@ [kind-of-widget-fallback-input-search-border-image-width-001.html] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-top-style-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-top-style-001.html.ini index 3802eb16..43ff37e2 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-top-style-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-top-style-001.html.ini
@@ -1,4 +1,5 @@ [kind-of-widget-fallback-input-search-border-top-style-001.html] expected: - if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-left-width-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-left-width-001.html.ini new file mode 100644 index 0000000..eebdec5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-left-width-001.html.ini
@@ -0,0 +1,3 @@ +[kind-of-widget-fallback-input-search-text-border-left-width-001.html] + expected: + if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-right-color-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-right-color-001.html.ini index 1bd0ccb..a819ba4 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-right-color-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-right-color-001.html.ini
@@ -1,4 +1,5 @@ [kind-of-widget-fallback-input-search-text-border-right-color-001.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL + if (product == "content_shell") and (os == "linux"): FAIL if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-start-end-radius-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-start-end-radius-001.html.ini index aff21c3..cc97131 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-start-end-radius-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-start-end-radius-001.html.ini
@@ -1,5 +1,6 @@ [kind-of-widget-fallback-input-search-text-border-start-end-radius-001.html] expected: if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-image-width-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-image-width-001.html.ini index 42d6729..8c5d305 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-image-width-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-image-width-001.html.ini
@@ -1,5 +1,6 @@ [kind-of-widget-fallback-input-submit-border-image-width-001.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL - if (product == "content_shell") and (os == "linux"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL if (product == "content_shell") and (os == "win"): FAIL + if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-text-background-position-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-text-background-position-001.html.ini index 65d5dc3e2..5210e36 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-text-background-position-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-text-background-position-001.html.ini
@@ -1,4 +1,5 @@ [kind-of-widget-fallback-input-text-background-position-001.html] expected: + if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-text-border-start-start-radius-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-text-border-start-start-radius-001.html.ini index f3ae270..7b971a3 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-text-border-start-start-radius-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-text-border-start-start-radius-001.html.ini
@@ -2,3 +2,4 @@ expected: if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL + if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-background-origin-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-background-origin-001.html.ini index 16e90e49e..e047124 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-background-origin-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-background-origin-001.html.ini
@@ -1,3 +1,4 @@ [kind-of-widget-fallback-textarea-background-origin-001.html] expected: if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL + if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-background-size-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-background-size-001.html.ini index cfd272d..00c06ea 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-background-size-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-background-size-001.html.ini
@@ -1,3 +1,4 @@ [kind-of-widget-fallback-textarea-background-size-001.html] expected: if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-block-end-style-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-block-end-style-001.html.ini index bcededb..6417297 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-block-end-style-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-block-end-style-001.html.ini
@@ -1,3 +1,4 @@ [kind-of-widget-fallback-textarea-border-block-end-style-001.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL + if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-block-start-color-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-block-start-color-001.html.ini index 52c13ea59..982ba82 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-block-start-color-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-block-start-color-001.html.ini
@@ -1,4 +1,5 @@ [kind-of-widget-fallback-textarea-border-block-start-color-001.html] expected: - if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL + if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL + if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-bottom-left-radius-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-bottom-left-radius-001.html.ini index 9908b87..370cb633 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-bottom-left-radius-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-bottom-left-radius-001.html.ini
@@ -1,4 +1,5 @@ [kind-of-widget-fallback-textarea-border-bottom-left-radius-001.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-bottom-width-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-bottom-width-001.html.ini index ec7a7ee..ef5066cf7 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-bottom-width-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-bottom-width-001.html.ini
@@ -1,3 +1,4 @@ [kind-of-widget-fallback-textarea-border-bottom-width-001.html] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL if (product == "content_shell") and (os == "win"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-image-width-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-image-width-001.html.ini index afe0cb8..ebbc272b 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-image-width-001.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-textarea-border-image-width-001.html.ini
@@ -1,4 +1,5 @@ [kind-of-widget-fallback-textarea-border-image-width-001.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL + if (product == "content_shell") and (os == "linux"): FAIL if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/abs-pos-non-replaced-vrl-006.xht.ini b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/abs-pos-non-replaced-vrl-006.xht.ini index 1cf7e9c..30383b9 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/abs-pos-non-replaced-vrl-006.xht.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/abs-pos-non-replaced-vrl-006.xht.ini
@@ -1,3 +1,4 @@ [abs-pos-non-replaced-vrl-006.xht] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/abs-pos-non-replaced-vrl-138.xht.ini b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/abs-pos-non-replaced-vrl-138.xht.ini new file mode 100644 index 0000000..6d0301b4 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/abs-pos-non-replaced-vrl-138.xht.ini
@@ -0,0 +1,3 @@ +[abs-pos-non-replaced-vrl-138.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/direction-vlr-003.xht.ini b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/direction-vlr-003.xht.ini index 683dee1..a3d46ad 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/direction-vlr-003.xht.ini +++ b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/direction-vlr-003.xht.ini
@@ -1,3 +1,4 @@ [direction-vlr-003.xht] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/horizontal-rule-vlr-003.xht.ini b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/horizontal-rule-vlr-003.xht.ini new file mode 100644 index 0000000..abe4294 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/horizontal-rule-vlr-003.xht.ini
@@ -0,0 +1,3 @@ +[horizontal-rule-vlr-003.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/sizing-orthog-prct-htb-in-vlr-001.xht.ini b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/sizing-orthog-prct-htb-in-vlr-001.xht.ini new file mode 100644 index 0000000..aaa6309 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/sizing-orthog-prct-htb-in-vlr-001.xht.ini
@@ -0,0 +1,3 @@ +[sizing-orthog-prct-htb-in-vlr-001.xht] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/css-filters-animation-blur.html.ini b/third_party/blink/web_tests/external/wpt/css/filter-effects/css-filters-animation-blur.html.ini index 5639214..bfb3d8a 100644 --- a/third_party/blink/web_tests/external/wpt/css/filter-effects/css-filters-animation-blur.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/css-filters-animation-blur.html.ini
@@ -1,4 +1,5 @@ [css-filters-animation-blur.html] expected: - if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/css-filters-animation-brightness.html.ini b/third_party/blink/web_tests/external/wpt/css/filter-effects/css-filters-animation-brightness.html.ini new file mode 100644 index 0000000..310a449 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/css-filters-animation-brightness.html.ini
@@ -0,0 +1,3 @@ +[css-filters-animation-brightness.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-feoffset-001.html.ini b/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-feoffset-001.html.ini new file mode 100644 index 0000000..b8aa56f --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-feoffset-001.html.ini
@@ -0,0 +1,3 @@ +[svg-feoffset-001.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/mediaqueries/relative-units-003.html.ini b/third_party/blink/web_tests/external/wpt/css/mediaqueries/relative-units-003.html.ini new file mode 100644 index 0000000..a4db1d6 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/mediaqueries/relative-units-003.html.ini
@@ -0,0 +1,3 @@ +[relative-units-003.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-within-011.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/focus-within-011.html.ini new file mode 100644 index 0000000..2ada8102 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-within-011.html.ini
@@ -0,0 +1,3 @@ +[focus-within-011.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/nth-child-in-shadow-root.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/nth-child-in-shadow-root.html.ini index 88137b2..ccf32d72 100644 --- a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/nth-child-in-shadow-root.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/nth-child-in-shadow-root.html.ini
@@ -1,7 +1,6 @@ [nth-child-in-shadow-root.html] expected: - if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL - if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL - if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL - if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL - if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL + if (product == "content_shell") and (os == "linux"): PASS + if (product == "content_shell") and (os == "win"): PASS + if product == "chrome": PASS + FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/nth-last-child-in-shadow-root.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/nth-last-child-in-shadow-root.html.ini index 964443b..2aed680a 100644 --- a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/nth-last-child-in-shadow-root.html.ini +++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/nth-last-child-in-shadow-root.html.ini
@@ -1,7 +1,6 @@ [nth-last-child-in-shadow-root.html] expected: if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): PASS - if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): PASS if (product == "content_shell") and (os == "linux"): PASS if product == "chrome": PASS FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/is-where-error-recovery-expected.txt b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-error-recovery-expected.txt new file mode 100644 index 0000000..f2ba708 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-error-recovery-expected.txt
@@ -0,0 +1,4 @@ +This is a testharness.js-based test. +FAIL CSS Selectors: :is() and :where() error recovery assert_equals: Should be parsed as-is (but not be considered valid) expected ":is(:total-nonsense)" but got ":is()" +Harness: the test ran to completion. +
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/is-where-error-recovery.html b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-error-recovery.html index 1d6e870e..f7e6f6f 100644 --- a/third_party/blink/web_tests/external/wpt/css/selectors/is-where-error-recovery.html +++ b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-error-recovery.html
@@ -23,18 +23,12 @@ "random-selector", "Should've parsed", ); - assert_not_equals( - rule.selectorText, - invalidSelector, - "Should not be considered valid and parsed as-is", - ); - let emptyList = `:${pseudo}()`; assert_equals( rule.selectorText, - emptyList, - "Should be serialized as an empty selector-list", + invalidSelector, + "Should be parsed as-is (but not be considered valid)", ); - assert_equals(document.querySelector(emptyList), null, "Should never match, but should parse"); + assert_equals(document.querySelector(rule.selectorText), null, "Should never match, but should parse"); for (let mixedList of [ `:${pseudo}(:total-nonsense, #test-div)`, `:${pseudo}(:total-nonsense and-more-stuff, #test-div)`, @@ -43,8 +37,8 @@ rule.selectorText = mixedList; assert_equals( rule.selectorText, - `:${pseudo}(#test-div)`, - `${mixedList}: Should ignore invalid selectors`, + mixedList, + `${mixedList}: Should parse invalid selectors`, ); let testDiv = document.getElementById("test-div"); assert_equals(document.querySelector(mixedList), testDiv, "Should correctly match");
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/is-where-error-recovery.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-error-recovery.html.ini new file mode 100644 index 0000000..f459e2c --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-error-recovery.html.ini
@@ -0,0 +1,5 @@ +[is-where-error-recovery.html] + [CSS Selectors: :is() and :where() error recovery] + expected: + if debug: PASS + FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/is-where-parsing-expected.txt b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-parsing-expected.txt new file mode 100644 index 0000000..6d7d5fc --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-parsing-expected.txt
@@ -0,0 +1,19 @@ +This is a testharness.js-based test. +PASS Trailing whitespace +PASS Multiple selectors with combinators +PASS Nested :is +PASS Nested :where +PASS Nested inside :host, without combinators +PASS Nested inside :host, with trailing whitespace +PASS Nested inside :host, with combinators +PASS Pseudo-classes inside +PASS Pseudo-classes after +PASS Pseudo-elements after +FAIL Pseudo-elements inside assert_equals: Pseudo-elements inside: :is(::before) expected ":is(::before)" but got ":is()" +PASS Combinators after +PASS After part with simple pseudo-class +PASS After part with invalid selector after +PASS Nested inside :not, without combinators +PASS Nested inside :not, with combinators +Harness: the test ran to completion. +
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/is-where-parsing.html b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-parsing.html index c9cc223..3159ecfe 100644 --- a/third_party/blink/web_tests/external/wpt/css/selectors/is-where-parsing.html +++ b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-parsing.html
@@ -10,7 +10,7 @@ </style> <script> let rule = document.getElementById("test-sheet").sheet.cssRules[0]; - function assert_valid(expected_valid, pattern, expected_pattern, description) { + function assert_valid(expected_parseable, pattern, expected_pattern, description) { test(function() { for (let pseudo of ["is", "where"]) { let selector = pattern.replace("{}", ":" + pseudo); @@ -19,7 +19,7 @@ expected_selector = expected_pattern.replace("{}", ":" + pseudo); rule.selectorText = "random-selector"; rule.selectorText = selector; - (expected_valid ? assert_equals : assert_not_equals)( + (expected_parseable ? assert_equals : assert_not_equals)( rule.selectorText, expected_selector, `${description}: ${selector}` @@ -42,7 +42,8 @@ assert_valid(true, "{}(:hover, :active)", null, "Pseudo-classes inside"); assert_valid(true, "{}(div):hover", null, "Pseudo-classes after"); assert_valid(true, "{}(div)::before", null, "Pseudo-elements after"); - assert_valid(false, "{}(::before)", null, "Pseudo-elements inside"); + // Should ask clarification from CSSWG + assert_valid(true, "{}(::before)", null, "Pseudo-elements inside"); assert_valid(true, "{}(div) + bar", null, "Combinators after"); assert_valid(true, "::part(foo):is(:hover)", null, "After part with simple pseudo-class");
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/is-where-parsing.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-parsing.html.ini new file mode 100644 index 0000000..dde6c5a --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/is-where-parsing.html.ini
@@ -0,0 +1,5 @@ +[is-where-parsing.html] + [Pseudo-elements inside] + expected: + if debug: PASS + FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/nth-child-of-complex-selector-many-children-2.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/nth-child-of-complex-selector-many-children-2.html.ini new file mode 100644 index 0000000..21d86f4 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/nth-child-of-complex-selector-many-children-2.html.ini
@@ -0,0 +1,3 @@ +[nth-child-of-complex-selector-many-children-2.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/nth-last-child-of-complex-selector.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/nth-last-child-of-complex-selector.html.ini new file mode 100644 index 0000000..1c12522c --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/nth-last-child-of-complex-selector.html.ini
@@ -0,0 +1,3 @@ +[nth-last-child-of-complex-selector.html] + expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-disallow-nesting-has-inside-has-expected.txt b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-disallow-nesting-has-inside-has-expected.txt new file mode 100644 index 0000000..66284e65 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-disallow-nesting-has-inside-has-expected.txt
@@ -0,0 +1,6 @@ +This is a testharness.js-based test. +PASS ".a:has(.b:has(.c))" should be an invalid selector +FAIL ".a:has(:is(.b:has(.c)))" should be a valid selector assert_equals: serialization should be canonical expected ".a:has(:is(.b:has(.c)))" but got ".a:has(:is())" +FAIL ".a:has(:is(.b:has(.c), .d))" should be a valid selector assert_equals: serialization should be canonical expected ".a:has(:is(.b:has(.c), .d))" but got ".a:has(:is(.d))" +Harness: the test ran to completion. +
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-disallow-nesting-has-inside-has.html b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-disallow-nesting-has-inside-has.html index 9cf989f..c5fcb589 100644 --- a/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-disallow-nesting-has-inside-has.html +++ b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-disallow-nesting-has-inside-has.html
@@ -8,6 +8,6 @@ <script src="/css/support/parsing-testcommon.js"></script> <script> test_invalid_selector('.a:has(.b:has(.c))'); - test_valid_selector('.a:has(:is(.b:has(.c)))', '.a:has(:is())'); - test_valid_selector('.a:has(:is(.b:has(.c), .d))', '.a:has(:is(.d))'); + test_valid_selector('.a:has(:is(.b:has(.c)))'); + test_valid_selector('.a:has(:is(.b:has(.c), .d))'); </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-disallow-nesting-has-inside-has.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-disallow-nesting-has-inside-has.html.ini new file mode 100644 index 0000000..c53ede9c --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-disallow-nesting-has-inside-has.html.ini
@@ -0,0 +1,10 @@ +[parse-has-disallow-nesting-has-inside-has.html] + [".a:has(:is(.b:has(.c)))" should be a valid selector] + expected: + if debug: PASS + FAIL + + [".a:has(:is(.b:has(.c), .d))" should be a valid selector] + expected: + if debug: PASS + FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-expected.txt b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-expected.txt new file mode 100644 index 0000000..e4c0d3a5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-expected.txt
@@ -0,0 +1,33 @@ +This is a testharness.js-based test. +PASS ":has(a)" should be a valid selector +PASS ":has(#a)" should be a valid selector +PASS ":has(.a)" should be a valid selector +PASS ":has([a])" should be a valid selector +PASS ":has([a=\"b\"])" should be a valid selector +PASS ":has([a|=\"b\"])" should be a valid selector +PASS ":has(:hover)" should be a valid selector +PASS "*:has(.a)" should be a valid selector +PASS ".a:has(.b)" should be a valid selector +PASS ".a:has(> .b)" should be a valid selector +PASS ".a:has(~ .b)" should be a valid selector +PASS ".a:has(+ .b)" should be a valid selector +PASS ".a:has(.b) .c" should be a valid selector +PASS ".a .b:has(.c)" should be a valid selector +PASS ".a .b:has(.c .d)" should be a valid selector +PASS ".a .b:has(.c .d) .e" should be a valid selector +PASS ".a:has(.b:is(.c .d))" should be a valid selector +PASS ".a:is(.b:has(.c) .d)" should be a valid selector +PASS ".a:not(:has(.b))" should be a valid selector +PASS ".a:has(:not(.b))" should be a valid selector +PASS ".a:has(.b):has(.c)" should be a valid selector +PASS "*|*:has(*)" should be a valid selector +PASS ":has(*|*)" should be a valid selector +PASS ":has" should be an invalid selector +PASS ".a:has" should be an invalid selector +PASS ".a:has b" should be an invalid selector +PASS ":has()" should be an invalid selector +PASS ":has(123)" should be an invalid selector +PASS ":has(.a, 123)" should be an invalid selector +FAIL ":has(:is(.a, 123))" should be a valid selector assert_equals: serialization should be canonical expected ":has(:is(.a, 123))" but got ":has(:is(.a))" +Harness: the test ran to completion. +
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html index 5d071010..902fc98 100644 --- a/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html +++ b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html
@@ -37,5 +37,5 @@ test_invalid_selector(':has()'); test_invalid_selector(':has(123)'); test_invalid_selector(':has(.a, 123)'); - test_valid_selector(':has(:is(.a, 123))', ':has(:is(.a))'); + test_valid_selector(':has(:is(.a, 123))'); </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html.ini new file mode 100644 index 0000000..277f97a --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html.ini
@@ -0,0 +1,5 @@ +[parse-has.html] + [":has(:is(.a, 123))" should be a valid selector] + expected: + if debug: PASS + FAIL
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html.ini b/third_party/blink/web_tests/external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html.ini index 580caad..dc795c3 100644 --- a/third_party/blink/web_tests/external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html.ini +++ b/third_party/blink/web_tests/external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html.ini
@@ -1,6 +1,8 @@ [ElementInternals-target-element-is-held-strongly.html] expected: - if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): TIMEOUT - if (product == "content_shell") and (os == "mac") and (port == "mac11"): TIMEOUT - if (product == "content_shell") and (os == "win"): TIMEOUT - if (product == "content_shell") and (os == "linux"): TIMEOUT + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): OK + if (product == "content_shell") and (os == "mac") and (port == "mac13"): OK + if (product == "content_shell") and (os == "mac") and (port == "mac12"): [TIMEOUT, OK] + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): OK + if product == "chrome": OK + TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-window.html.ini b/third_party/blink/web_tests/external/wpt/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-window.html.ini index 64d3e8b..6cf0854f 100644 --- a/third_party/blink/web_tests/external/wpt/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-window.html.ini +++ b/third_party/blink/web_tests/external/wpt/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-window.html.ini
@@ -1,12 +1,12 @@ [passive-touchstart-event-listener-on-window.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): TIMEOUT - if (product == "content_shell") and (os == "mac") and (port == "mac11"): [TIMEOUT, OK] - if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): TIMEOUT if (product == "content_shell") and (os == "mac") and (port == "mac12"): TIMEOUT + if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): TIMEOUT if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): TIMEOUT + if (product == "content_shell") and (os == "mac") and (port == "mac13"): [TIMEOUT, OK] + if (product == "content_shell") and (os == "mac") and (port == "mac11"): TIMEOUT [passive touchstart event listener on window] expected: - if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL - if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL if (product == "content_shell") and (os == "linux"): FAIL + if (product == "content_shell") and (os == "win"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/dom/events/scrolling/overscroll-deltas.html.ini b/third_party/blink/web_tests/external/wpt/dom/events/scrolling/overscroll-deltas.html.ini index fa86d27..88a71b0f 100644 --- a/third_party/blink/web_tests/external/wpt/dom/events/scrolling/overscroll-deltas.html.ini +++ b/third_party/blink/web_tests/external/wpt/dom/events/scrolling/overscroll-deltas.html.ini
@@ -4,7 +4,8 @@ if product == "chrome": ERROR [testing, horizontal] expected: - if (product == "content_shell") and (os == "mac") and (port == "mac12"): [FAIL, PASS] + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS] + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL if product == "chrome": FAIL [testing, vertical]
diff --git a/third_party/blink/web_tests/external/wpt/dom/events/webkit-animation-iteration-event.html.ini b/third_party/blink/web_tests/external/wpt/dom/events/webkit-animation-iteration-event.html.ini index 529abf2..f60214a 100644 --- a/third_party/blink/web_tests/external/wpt/dom/events/webkit-animation-iteration-event.html.ini +++ b/third_party/blink/web_tests/external/wpt/dom/events/webkit-animation-iteration-event.html.ini
@@ -1,7 +1,8 @@ [webkit-animation-iteration-event.html] expected: - if (product == "content_shell") and (os == "mac") and (port == "mac13"): TIMEOUT if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): TIMEOUT + if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): [TIMEOUT, OK] if (product == "content_shell") and (os == "mac") and (port == "mac11"): TIMEOUT + if (product == "content_shell") and (os == "mac") and (port == "mac13"): TIMEOUT if (product == "content_shell") and (os == "mac") and (port == "mac12"): TIMEOUT - if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [TIMEOUT, OK] + if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/disconnect-image.html.ini b/third_party/blink/web_tests/external/wpt/element-timing/disconnect-image.html.ini index 28a97ad..4b8f9f88 100644 --- a/third_party/blink/web_tests/external/wpt/element-timing/disconnect-image.html.ini +++ b/third_party/blink/web_tests/external/wpt/element-timing/disconnect-image.html.ini
@@ -1,4 +1,4 @@ [disconnect-image.html] [Disconnected elements have null as their |element| attribute.] expected: - if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL + if (product == "content_shell") and (os == "win"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-protocol-setter-non-broken-weird-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-protocol-setter-non-broken-weird-expected.txt deleted file mode 100644 index 1e962bb..0000000 --- a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-protocol-setter-non-broken-weird-expected.txt +++ /dev/null
@@ -1,8 +0,0 @@ -This is a testharness.js-based test. -PASS Set location.protocol to x -FAIL Set location.protocol to data Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame. -PASS Set location.protocol to file -PASS Set location.protocol to ftp -PASS Set location.protocol to http+x -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/documents/resource-metadata-management/document-cookie-expected.txt b/third_party/blink/web_tests/external/wpt/html/dom/documents/resource-metadata-management/document-cookie-expected.txt deleted file mode 100644 index dab2e9e..0000000 --- a/third_party/blink/web_tests/external/wpt/html/dom/documents/resource-metadata-management/document-cookie-expected.txt +++ /dev/null
@@ -1,8 +0,0 @@ -This is a testharness.js-based test. -PASS document has no cookie -PASS document.cookie -PASS document.cookie 1 -FAIL document.cookie 2 assert_equals: expected "" but got "b=A" -PASS getting cookie for a cookie-averse document returns empty string, setting does nothing -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/search-styles-expected.txt b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/search-styles-expected.txt index f9926c8..c2dea86d 100644 --- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/search-styles-expected.txt +++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/search-styles-expected.txt
@@ -1,5 +1,5 @@ This is a testharness.js-based test. -FAIL <search> - display assert_equals: expected "block" but got "inline" +PASS <search> - display PASS <search> - margin-top PASS <search> - margin-right PASS <search> - margin-bottom
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/search-styles-iso-8859-8-expected.txt b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/search-styles-iso-8859-8-expected.txt index 373390b..afd37c3f 100644 --- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/search-styles-iso-8859-8-expected.txt +++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/search-styles-iso-8859-8-expected.txt
@@ -1,6 +1,6 @@ This is a testharness.js-based test. PASS Verify document.characterSet is ISO-8859-8 -FAIL <search> - display assert_equals: expected "block" but got "inline" +PASS <search> - display PASS <search> - margin-top PASS <search> - margin-right PASS <search> - margin-bottom
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-transform-translatez.html.ini b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-transform-translatez.html.ini index e926387..9572544f 100644 --- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-transform-translatez.html.ini +++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-transform-translatez.html.ini
@@ -1,4 +1,4 @@ [fieldset-transform-translatez.html] expected: - if (product == "content_shell") and (os == "win") and (port == "win11"): FAIL if (product == "content_shell") and (os == "linux"): FAIL + if (product == "content_shell") and (os == "win"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-dim.html.ini b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-dim.html.ini index d6a1c3a1d..d78081e 100644 --- a/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-dim.html.ini +++ b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-dim.html.ini
@@ -1,3 +1,4 @@ [img-dim.html] expected: + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/widgets/the-select-element/option-only-label.html.ini b/third_party/blink/web_tests/external/wpt/html/rendering/widgets/the-select-element/option-only-label.html.ini index c2de9ed..60c4b4ea 100644 --- a/third_party/blink/web_tests/external/wpt/html/rendering/widgets/the-select-element/option-only-label.html.ini +++ b/third_party/blink/web_tests/external/wpt/html/rendering/widgets/the-select-element/option-only-label.html.ini
@@ -1,3 +1,4 @@ [option-only-label.html] expected: if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-site-isolation-trials"): FAIL + if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/syntax/parsing/html5lib_search-element_run_type=uri-expected.txt b/third_party/blink/web_tests/external/wpt/html/syntax/parsing/html5lib_search-element_run_type=uri-expected.txt deleted file mode 100644 index 67fc2011..0000000 --- a/third_party/blink/web_tests/external/wpt/html/syntax/parsing/html5lib_search-element_run_type=uri-expected.txt +++ /dev/null
@@ -1,6 +0,0 @@ -This is a testharness.js-based test. -FAIL html5lib_search-element.html 114b9f3c8147c0ed8ef3ed1811a9da3f10d74402 assert_equals: expected "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <p>\n| \"foo\"\n| <search>\n| \"bar\"\n| <p>\n| \"baz\"" but got "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <p>\n| \"foo\"\n| <search>\n| \"bar\"\n| <p>\n| \"baz\"" -FAIL html5lib_search-element.html 2204afb9037ec886f428ab5dcead5ee9f87c65cb assert_equals: expected "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <search>\n| <p>\n| \"foo\"\n| \"bar\"" but got "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <search>\n| <p>\n| \"foobar\"" -PASS html5lib_search-element.html 5153f797fbb63a23a40d19e298aca06d53d22f7f -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/html/syntax/parsing/html5lib_search-element_run_type=write-expected.txt b/third_party/blink/web_tests/external/wpt/html/syntax/parsing/html5lib_search-element_run_type=write-expected.txt deleted file mode 100644 index 67fc2011..0000000 --- a/third_party/blink/web_tests/external/wpt/html/syntax/parsing/html5lib_search-element_run_type=write-expected.txt +++ /dev/null
@@ -1,6 +0,0 @@ -This is a testharness.js-based test. -FAIL html5lib_search-element.html 114b9f3c8147c0ed8ef3ed1811a9da3f10d74402 assert_equals: expected "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <p>\n| \"foo\"\n| <search>\n| \"bar\"\n| <p>\n| \"baz\"" but got "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <p>\n| \"foo\"\n| <search>\n| \"bar\"\n| <p>\n| \"baz\"" -FAIL html5lib_search-element.html 2204afb9037ec886f428ab5dcead5ee9f87c65cb assert_equals: expected "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <search>\n| <p>\n| \"foo\"\n| \"bar\"" but got "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <search>\n| <p>\n| \"foobar\"" -PASS html5lib_search-element.html 5153f797fbb63a23a40d19e298aca06d53d22f7f -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/html/syntax/parsing/html5lib_search-element_run_type=write_single-expected.txt b/third_party/blink/web_tests/external/wpt/html/syntax/parsing/html5lib_search-element_run_type=write_single-expected.txt deleted file mode 100644 index 67fc2011..0000000 --- a/third_party/blink/web_tests/external/wpt/html/syntax/parsing/html5lib_search-element_run_type=write_single-expected.txt +++ /dev/null
@@ -1,6 +0,0 @@ -This is a testharness.js-based test. -FAIL html5lib_search-element.html 114b9f3c8147c0ed8ef3ed1811a9da3f10d74402 assert_equals: expected "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <p>\n| \"foo\"\n| <search>\n| \"bar\"\n| <p>\n| \"baz\"" but got "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <p>\n| \"foo\"\n| <search>\n| \"bar\"\n| <p>\n| \"baz\"" -FAIL html5lib_search-element.html 2204afb9037ec886f428ab5dcead5ee9f87c65cb assert_equals: expected "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <search>\n| <p>\n| \"foo\"\n| \"bar\"" but got "#document\n| <!DOCTYPE html>\n| <html>\n| <head>\n| <body>\n| <search>\n| <p>\n| \"foobar\"" -PASS html5lib_search-element.html 5153f797fbb63a23a40d19e298aca06d53d22f7f -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-svg-data-uri-background-image.html.ini b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-svg-data-uri-background-image.html.ini new file mode 100644 index 0000000..2a2a6d0 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-svg-data-uri-background-image.html.ini
@@ -0,0 +1,4 @@ +[observe-svg-data-uri-background-image.html] + [Data-URI background SVG image is observable.] + expected: + if (product == "content_shell") and (os == "win") and (port == "win11"): [FAIL, PASS]
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/scripts/stretchy-munderover-2d.html.ini b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/scripts/stretchy-munderover-2d.html.ini new file mode 100644 index 0000000..e90a3365 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/scripts/stretchy-munderover-2d.html.ini
@@ -0,0 +1,3 @@ +[stretchy-munderover-2d.html] + expected: + if (product == "chrome") and not debug: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-extensions/MediaStreamTrack-getFrameStats.https.html.ini b/third_party/blink/web_tests/external/wpt/mediacapture-extensions/MediaStreamTrack-getFrameStats.https.html.ini new file mode 100644 index 0000000..4802514 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/mediacapture-extensions/MediaStreamTrack-getFrameStats.https.html.ini
@@ -0,0 +1,13 @@ +[MediaStreamTrack-getFrameStats.https.html] + [Stats are frozen while disabled] + expected: + if debug: PASS + FAIL + + [discardedFrames increases when frameRate decimation is happening] + expected: + if (product == "content_shell") and (os == "win") and (port == "win11"): [PASS, FAIL] + if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): [PASS, FAIL] + if (product == "content_shell") and (os == "mac") and (port == "mac13"): [PASS, FAIL] + if (product == "content_shell") and (os == "mac") and (port == "mac12"): [PASS, FAIL] + if (product == "content_shell") and (os == "mac") and (port == "mac11"): [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouseevent_key_pressed.html.ini b/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouseevent_key_pressed.html.ini index 982f5a2..36fcef1 100644 --- a/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouseevent_key_pressed.html.ini +++ b/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouseevent_key_pressed.html.ini
@@ -1,10 +1,10 @@ [pointerevent_mouseevent_key_pressed.html] expected: - if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): TIMEOUT - if (product == "content_shell") and (os == "mac") and (port == "mac12"): TIMEOUT if (product == "content_shell") and (os == "mac") and (port == "mac11"): TIMEOUT + if (product == "content_shell") and (os == "mac") and (port == "mac12"): TIMEOUT if (product == "content_shell") and (os == "mac") and (port == "mac13"): TIMEOUT if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): TIMEOUT + if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): TIMEOUT if product == "chrome": OK ERROR [Tests that the mouse events with some keys pressed.]
diff --git a/third_party/blink/web_tests/external/wpt/svg/extensibility/foreignObject/will-change-in-transformed-foreign-object.html.ini b/third_party/blink/web_tests/external/wpt/svg/extensibility/foreignObject/will-change-in-transformed-foreign-object.html.ini index 85b4ad5d..8e65914c 100644 --- a/third_party/blink/web_tests/external/wpt/svg/extensibility/foreignObject/will-change-in-transformed-foreign-object.html.ini +++ b/third_party/blink/web_tests/external/wpt/svg/extensibility/foreignObject/will-change-in-transformed-foreign-object.html.ini
@@ -1,3 +1,4 @@ [will-change-in-transformed-foreign-object.html] expected: if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL + if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/svg/linking/reftests/view-viewbox-override.html.ini b/third_party/blink/web_tests/external/wpt/svg/linking/reftests/view-viewbox-override.html.ini index 60ada3c..c6576d6 100644 --- a/third_party/blink/web_tests/external/wpt/svg/linking/reftests/view-viewbox-override.html.ini +++ b/third_party/blink/web_tests/external/wpt/svg/linking/reftests/view-viewbox-override.html.ini
@@ -1,5 +1,4 @@ [view-viewbox-override.html] expected: - if not debug and (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): PASS if debug: PASS FAIL
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window_include=file-expected.txt b/third_party/blink/web_tests/external/wpt/url/url-setters-a-area.window_include=file-expected.txt similarity index 94% rename from third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window_include=file-expected.txt rename to third_party/blink/web_tests/external/wpt/url/url-setters-a-area.window_include=file-expected.txt index f999cb5..1d952514 100644 --- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window_include=file-expected.txt +++ b/third_party/blink/web_tests/external/wpt/url/url-setters-a-area.window_include=file-expected.txt
@@ -6,8 +6,8 @@ PASS <area>: Setting <file:///test>.protocol = 'https' PASS <a>: Setting <file:>.protocol = 'wss' PASS <area>: Setting <file:>.protocol = 'wss' -FAIL <a>: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path" -FAIL <area>: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path" +PASS <a>: Setting <file://hi/path>.protocol = 's' +PASS <area>: Setting <file://hi/path>.protocol = 's' PASS <a>: Setting <file:///home/you/index.html>.username = 'me' No host means no username PASS <area>: Setting <file:///home/you/index.html>.username = 'me' No host means no username PASS <a>: Setting <file://test/>.username = 'test'
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker_include=file-expected.txt b/third_party/blink/web_tests/external/wpt/url/url-setters.any.worker_include=file-expected.txt similarity index 93% rename from third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker_include=file-expected.txt rename to third_party/blink/web_tests/external/wpt/url/url-setters.any.worker_include=file-expected.txt index 3c1b7ea..3f7e167 100644 --- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker_include=file-expected.txt +++ b/third_party/blink/web_tests/external/wpt/url/url-setters.any.worker_include=file-expected.txt
@@ -3,7 +3,7 @@ FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" PASS URL: Setting <file:///test>.protocol = 'https' PASS URL: Setting <file:>.protocol = 'wss' -FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "file:///S://hi/path" +PASS URL: Setting <file://hi/path>.protocol = 's' PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username PASS URL: Setting <file://test/>.username = 'test' PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker_include=file-expected.txt b/third_party/blink/web_tests/external/wpt/url/url-setters.any_include=file-expected.txt similarity index 93% copy from third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker_include=file-expected.txt copy to third_party/blink/web_tests/external/wpt/url/url-setters.any_include=file-expected.txt index 3c1b7ea..3f7e167 100644 --- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker_include=file-expected.txt +++ b/third_party/blink/web_tests/external/wpt/url/url-setters.any_include=file-expected.txt
@@ -3,7 +3,7 @@ FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" PASS URL: Setting <file:///test>.protocol = 'https' PASS URL: Setting <file:>.protocol = 'wss' -FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "file:///S://hi/path" +PASS URL: Setting <file://hi/path>.protocol = 's' PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username PASS URL: Setting <file://test/>.username = 'test' PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password
diff --git a/third_party/blink/web_tests/fast/dom/HTMLAnchorElement/set-href-attribute-protocol.html b/third_party/blink/web_tests/fast/dom/HTMLAnchorElement/set-href-attribute-protocol.html index c5ab954..8192490 100644 --- a/third_party/blink/web_tests/fast/dom/HTMLAnchorElement/set-href-attribute-protocol.html +++ b/third_party/blink/web_tests/fast/dom/HTMLAnchorElement/set-href-attribute-protocol.html
@@ -12,7 +12,7 @@ debug("Basic test"); a.href = "https://www.mydomain.com/path/"; a.protocol = "http-foo"; -shouldBe("a.href", "'http-foo://www.mydomain.com/path/'"); +shouldBe("a.href", "'https://www.mydomain.com/path/'"); // IE8 throws "Invalid argument" exception. debug("Set a protocol that contains ':'"); @@ -54,7 +54,7 @@ debug("Set protocol to null"); a.href = "https://www.mydomain.com/path/"; a.protocol = null; -shouldBe("a.href", "'null://www.mydomain.com/path/'"); +shouldBe("a.href", "'https://www.mydomain.com/path/'"); // IE8 throws "Invalid argument" exception. try { @@ -81,7 +81,7 @@ debug("Set protocol to undefined"); a.href = "https://www.mydomain.com/path/"; a.protocol = undefined; -shouldBe("a.href", "'undefined://www.mydomain.com/path/'"); +shouldBe("a.href", "'https://www.mydomain.com/path/'"); </script> </body> </html>
diff --git "a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window_exclude=\050file_javascript_mailto\051-expected.txt" "b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window_exclude=\050file_javascript_mailto\051-expected.txt" index dc2bf5f2..46eb200 100644 --- "a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window_exclude=\050file_javascript_mailto\051-expected.txt" +++ "b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window_exclude=\050file_javascript_mailto\051-expected.txt"
@@ -1,5 +1,5 @@ This is a testharness.js-based test. -Found 443 tests; 303 PASS, 140 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 443 tests; 309 PASS, 134 FAIL, 0 TIMEOUT, 0 NOTRUN. PASS Loading data… PASS <a>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. PASS <area>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. @@ -25,12 +25,12 @@ PASS <area>: Setting <https://example.net:1234>.protocol = 'file' PASS <a>: Setting <wss://x:x@example.net:1234>.protocol = 'file' PASS <area>: Setting <wss://x:x@example.net:1234>.protocol = 'file' -FAIL <a>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/" -FAIL <area>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/" -FAIL <a>: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/" -FAIL <area>: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/" -FAIL <a>: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/" -FAIL <area>: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/" +PASS <a>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special +PASS <area>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special +PASS <a>: Setting <https://example.net>.protocol = 's' +PASS <area>: Setting <https://example.net>.protocol = 's' +PASS <a>: Setting <ftp://example.net>.protocol = 'test' +PASS <area>: Setting <ftp://example.net>.protocol = 'test' FAIL <a>: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/" FAIL <area>: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/" FAIL <a>: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/"
diff --git "a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt" "b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt" index 043c305e..c47e66e 100644 --- "a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt" +++ "b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt"
@@ -1,5 +1,5 @@ This is a testharness.js-based test. -Found 222 tests; 148 PASS, 74 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 222 tests; 151 PASS, 71 FAIL, 0 TIMEOUT, 0 NOTRUN. PASS Loading data… PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. PASS URL: Setting <a://example.net>.protocol = 'b' @@ -13,9 +13,9 @@ PASS URL: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file PASS URL: Setting <https://example.net:1234>.protocol = 'file' PASS URL: Setting <wss://x:x@example.net:1234>.protocol = 'file' -FAIL URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/" -FAIL URL: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/" -FAIL URL: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/" +PASS URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special +PASS URL: Setting <https://example.net>.protocol = 's' +PASS URL: Setting <ftp://example.net>.protocol = 'test' FAIL URL: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker_include=file-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker_include=file-expected.txt deleted file mode 100644 index 36f5583..0000000 --- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker_include=file-expected.txt +++ /dev/null
@@ -1,25 +0,0 @@ -This is a testharness.js-based test. -PASS Loading data… -FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" -PASS URL: Setting <file:///test>.protocol = 'https' -PASS URL: Setting <file:>.protocol = 'wss' -FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path" -PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username -PASS URL: Setting <file://test/>.username = 'test' -PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password -PASS URL: Setting <file://test/>.password = 'test' -FAIL URL: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/" -FAIL URL: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL URL: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS URL: Setting <file://y/>.hostname = 'x:123' -FAIL URL: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL URL: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS URL: Setting <file://test/>.port = '12' -FAIL URL: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/" -PASS URL: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased -PASS URL: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes -PASS URL: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes -PASS URL: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes -PASS URL: Setting <file:///var/log/system.log>.href = 'http://0300.168.0xF0' -Harness: the test ran to completion. -
diff --git "a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt" "b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt" index 043c305e..c47e66e 100644 --- "a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt" +++ "b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt"
@@ -1,5 +1,5 @@ This is a testharness.js-based test. -Found 222 tests; 148 PASS, 74 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 222 tests; 151 PASS, 71 FAIL, 0 TIMEOUT, 0 NOTRUN. PASS Loading data… PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. PASS URL: Setting <a://example.net>.protocol = 'b' @@ -13,9 +13,9 @@ PASS URL: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file PASS URL: Setting <https://example.net:1234>.protocol = 'file' PASS URL: Setting <wss://x:x@example.net:1234>.protocol = 'file' -FAIL URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/" -FAIL URL: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/" -FAIL URL: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/" +PASS URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special +PASS URL: Setting <https://example.net>.protocol = 's' +PASS URL: Setting <ftp://example.net>.protocol = 'test' FAIL URL: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any_include=file-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any_include=file-expected.txt deleted file mode 100644 index 36f5583..0000000 --- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any_include=file-expected.txt +++ /dev/null
@@ -1,25 +0,0 @@ -This is a testharness.js-based test. -PASS Loading data… -FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" -PASS URL: Setting <file:///test>.protocol = 'https' -PASS URL: Setting <file:>.protocol = 'wss' -FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path" -PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username -PASS URL: Setting <file://test/>.username = 'test' -PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password -PASS URL: Setting <file://test/>.password = 'test' -FAIL URL: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/" -FAIL URL: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL URL: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS URL: Setting <file://y/>.hostname = 'x:123' -FAIL URL: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL URL: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS URL: Setting <file://test/>.port = '12' -FAIL URL: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/" -PASS URL: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased -PASS URL: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes -PASS URL: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes -PASS URL: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes -PASS URL: Setting <file:///var/log/system.log>.href = 'http://0300.168.0xF0' -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/platform/linux/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt b/third_party/blink/web_tests/platform/linux/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt index f84485f7..8e4b3bcb2 100644 --- a/third_party/blink/web_tests/platform/linux/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt +++ b/third_party/blink/web_tests/platform/linux/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt
@@ -3,7 +3,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". Basic test -PASS a.href is 'http-foo://www.mydomain.com/path/' +PASS a.href is 'https://www.mydomain.com/path/' Set a protocol that contains ':' PASS a.href is 'http://www.mydomain.com/path/' Set a protocol that contains invalid characters @@ -13,7 +13,7 @@ Set a protocol that starts with ':' PASS a.href is 'https://www.mydomain.com/path/' Set protocol to null -PASS a.href is 'null://www.mydomain.com/path/' +PASS a.href is 'https://www.mydomain.com/path/' Set protocol to empty string PASS a.href is 'https://www.mydomain.com/path/' Set protocol to http on malformed URL @@ -21,7 +21,7 @@ Set protocol to a URL which points to a local file PASS a.href is 'f-oo:path' Set protocol to undefined -PASS a.href is 'undefined://www.mydomain.com/path/' +PASS a.href is 'https://www.mydomain.com/path/' PASS successfullyParsed is true TEST COMPLETE
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window_include=file-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window_include=file-expected.txt deleted file mode 100644 index f999cb5..0000000 --- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window_include=file-expected.txt +++ /dev/null
@@ -1,46 +0,0 @@ -This is a testharness.js-based test. -PASS Loading data… -FAIL <a>: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" -FAIL <area>: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" -PASS <a>: Setting <file:///test>.protocol = 'https' -PASS <area>: Setting <file:///test>.protocol = 'https' -PASS <a>: Setting <file:>.protocol = 'wss' -PASS <area>: Setting <file:>.protocol = 'wss' -FAIL <a>: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path" -FAIL <area>: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path" -PASS <a>: Setting <file:///home/you/index.html>.username = 'me' No host means no username -PASS <area>: Setting <file:///home/you/index.html>.username = 'me' No host means no username -PASS <a>: Setting <file://test/>.username = 'test' -PASS <area>: Setting <file://test/>.username = 'test' -PASS <a>: Setting <file:///home/me/index.html>.password = 'secret' No host means no password -PASS <area>: Setting <file:///home/me/index.html>.password = 'secret' No host means no password -PASS <a>: Setting <file://test/>.password = 'test' -PASS <area>: Setting <file://test/>.password = 'test' -FAIL <a>: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/" -FAIL <area>: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/" -FAIL <a>: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL <area>: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL <a>: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x" -FAIL <area>: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS <a>: Setting <file://y/>.hostname = 'x:123' -PASS <area>: Setting <file://y/>.hostname = 'x:123' -FAIL <a>: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL <area>: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL <a>: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x" -FAIL <area>: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS <a>: Setting <file://test/>.port = '12' -PASS <area>: Setting <file://test/>.port = '12' -FAIL <a>: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/" -FAIL <area>: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/" -PASS <a>: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased -PASS <area>: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased -PASS <a>: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes -PASS <area>: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes -FAIL <a>: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes assert_equals: expected "file://////" but got "file:///" -FAIL <area>: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes assert_equals: expected "file://////" but got "file:///" -FAIL <a>: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes assert_equals: expected "file://///" but got "file:///" -FAIL <area>: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes assert_equals: expected "file://///" but got "file:///" -PASS <a>: Setting <file:///var/log/system.log>.href = 'http://0300.168.0xF0' -PASS <area>: Setting <file:///var/log/system.log>.href = 'http://0300.168.0xF0' -Harness: the test ran to completion. -
diff --git "a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt" "b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt" index 043c305e..c47e66e 100644 --- "a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt" +++ "b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt"
@@ -1,5 +1,5 @@ This is a testharness.js-based test. -Found 222 tests; 148 PASS, 74 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 222 tests; 151 PASS, 71 FAIL, 0 TIMEOUT, 0 NOTRUN. PASS Loading data… PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. PASS URL: Setting <a://example.net>.protocol = 'b' @@ -13,9 +13,9 @@ PASS URL: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file PASS URL: Setting <https://example.net:1234>.protocol = 'file' PASS URL: Setting <wss://x:x@example.net:1234>.protocol = 'file' -FAIL URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/" -FAIL URL: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/" -FAIL URL: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/" +PASS URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special +PASS URL: Setting <https://example.net>.protocol = 's' +PASS URL: Setting <ftp://example.net>.protocol = 'test' FAIL URL: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker_include=file-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker_include=file-expected.txt deleted file mode 100644 index 36f5583..0000000 --- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker_include=file-expected.txt +++ /dev/null
@@ -1,25 +0,0 @@ -This is a testharness.js-based test. -PASS Loading data… -FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" -PASS URL: Setting <file:///test>.protocol = 'https' -PASS URL: Setting <file:>.protocol = 'wss' -FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path" -PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username -PASS URL: Setting <file://test/>.username = 'test' -PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password -PASS URL: Setting <file://test/>.password = 'test' -FAIL URL: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/" -FAIL URL: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL URL: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS URL: Setting <file://y/>.hostname = 'x:123' -FAIL URL: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL URL: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS URL: Setting <file://test/>.port = '12' -FAIL URL: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/" -PASS URL: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased -PASS URL: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes -PASS URL: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes -PASS URL: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes -PASS URL: Setting <file:///var/log/system.log>.href = 'http://0300.168.0xF0' -Harness: the test ran to completion. -
diff --git "a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt" "b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt" index 043c305e..c47e66e 100644 --- "a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt" +++ "b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt"
@@ -1,5 +1,5 @@ This is a testharness.js-based test. -Found 222 tests; 148 PASS, 74 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 222 tests; 151 PASS, 71 FAIL, 0 TIMEOUT, 0 NOTRUN. PASS Loading data… PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. PASS URL: Setting <a://example.net>.protocol = 'b' @@ -13,9 +13,9 @@ PASS URL: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file PASS URL: Setting <https://example.net:1234>.protocol = 'file' PASS URL: Setting <wss://x:x@example.net:1234>.protocol = 'file' -FAIL URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/" -FAIL URL: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/" -FAIL URL: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/" +PASS URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special +PASS URL: Setting <https://example.net>.protocol = 's' +PASS URL: Setting <ftp://example.net>.protocol = 'test' FAIL URL: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any_include=file-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any_include=file-expected.txt deleted file mode 100644 index 36f5583..0000000 --- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any_include=file-expected.txt +++ /dev/null
@@ -1,25 +0,0 @@ -This is a testharness.js-based test. -PASS Loading data… -FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" -PASS URL: Setting <file:///test>.protocol = 'https' -PASS URL: Setting <file:>.protocol = 'wss' -FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path" -PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username -PASS URL: Setting <file://test/>.username = 'test' -PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password -PASS URL: Setting <file://test/>.password = 'test' -FAIL URL: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/" -FAIL URL: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL URL: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS URL: Setting <file://y/>.hostname = 'x:123' -FAIL URL: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL URL: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS URL: Setting <file://test/>.port = '12' -FAIL URL: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/" -PASS URL: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased -PASS URL: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes -PASS URL: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes -PASS URL: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes -PASS URL: Setting <file:///var/log/system.log>.href = 'http://0300.168.0xF0' -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt b/third_party/blink/web_tests/platform/mac/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt similarity index 84% rename from third_party/blink/web_tests/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt rename to third_party/blink/web_tests/platform/mac/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt index f84485f7..8e4b3bcb2 100644 --- a/third_party/blink/web_tests/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt +++ b/third_party/blink/web_tests/platform/mac/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt
@@ -3,7 +3,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". Basic test -PASS a.href is 'http-foo://www.mydomain.com/path/' +PASS a.href is 'https://www.mydomain.com/path/' Set a protocol that contains ':' PASS a.href is 'http://www.mydomain.com/path/' Set a protocol that contains invalid characters @@ -13,7 +13,7 @@ Set a protocol that starts with ':' PASS a.href is 'https://www.mydomain.com/path/' Set protocol to null -PASS a.href is 'null://www.mydomain.com/path/' +PASS a.href is 'https://www.mydomain.com/path/' Set protocol to empty string PASS a.href is 'https://www.mydomain.com/path/' Set protocol to http on malformed URL @@ -21,7 +21,7 @@ Set protocol to a URL which points to a local file PASS a.href is 'f-oo:path' Set protocol to undefined -PASS a.href is 'undefined://www.mydomain.com/path/' +PASS a.href is 'https://www.mydomain.com/path/' PASS successfullyParsed is true TEST COMPLETE
diff --git "a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window_exclude=\050file_javascript_mailto\051-expected.txt" "b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window_exclude=\050file_javascript_mailto\051-expected.txt" index 470b693..dff2cc9 100644 --- "a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window_exclude=\050file_javascript_mailto\051-expected.txt" +++ "b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window_exclude=\050file_javascript_mailto\051-expected.txt"
@@ -1,20 +1,20 @@ This is a testharness.js-based test. -Found 443 tests; 283 PASS, 160 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 443 tests; 289 PASS, 154 FAIL, 0 TIMEOUT, 0 NOTRUN. PASS Loading data… FAIL <a>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL <area>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. assert_equals: expected "a://example.net" but got "file:///A://example.net" -FAIL <a>: Setting <a://example.net>.protocol = 'b' assert_equals: expected "b://example.net" but got "file:///B:///A://example.net" -FAIL <area>: Setting <a://example.net>.protocol = 'b' assert_equals: expected "b://example.net" but got "file:///B:///A://example.net" -FAIL <a>: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased assert_equals: expected "b://example.net" but got "file:///B:///A://example.net" -FAIL <area>: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased assert_equals: expected "b://example.net" but got "file:///B:///A://example.net" +FAIL <a>: Setting <a://example.net>.protocol = 'b' assert_equals: expected "b://example.net" but got "file:///A://example.net" +FAIL <area>: Setting <a://example.net>.protocol = 'b' assert_equals: expected "b://example.net" but got "file:///A://example.net" +FAIL <a>: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased assert_equals: expected "b://example.net" but got "file:///A://example.net" +FAIL <area>: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased assert_equals: expected "b://example.net" but got "file:///A://example.net" FAIL <a>: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL <area>: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL <a>: Setting <a://example.net>.protocol = '0b' No leading digit assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL <area>: Setting <a://example.net>.protocol = '0b' No leading digit assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL <a>: Setting <a://example.net>.protocol = '+b' No leading punctuation assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL <area>: Setting <a://example.net>.protocol = '+b' No leading punctuation assert_equals: expected "a://example.net" but got "file:///A://example.net" -FAIL <a>: Setting <a://example.net>.protocol = 'bC0+-.' assert_equals: expected "bc0+-.://example.net" but got "bc0+-.:///A://example.net" -FAIL <area>: Setting <a://example.net>.protocol = 'bC0+-.' assert_equals: expected "bc0+-.://example.net" but got "bc0+-.:///A://example.net" +FAIL <a>: Setting <a://example.net>.protocol = 'bC0+-.' assert_equals: expected "bc0+-.://example.net" but got "file:///A://example.net" +FAIL <area>: Setting <a://example.net>.protocol = 'bC0+-.' assert_equals: expected "bc0+-.://example.net" but got "file:///A://example.net" FAIL <a>: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL <area>: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL <a>: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected assert_equals: expected "a://example.net" but got "file:///A://example.net" @@ -25,12 +25,12 @@ PASS <area>: Setting <https://example.net:1234>.protocol = 'file' PASS <a>: Setting <wss://x:x@example.net:1234>.protocol = 'file' PASS <area>: Setting <wss://x:x@example.net:1234>.protocol = 'file' -FAIL <a>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "file:///B://example.net/" -FAIL <area>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "file:///B://example.net/" -FAIL <a>: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "file:///S://example.net/" -FAIL <area>: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "file:///S://example.net/" -FAIL <a>: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/" -FAIL <area>: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/" +PASS <a>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special +PASS <area>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special +PASS <a>: Setting <https://example.net>.protocol = 's' +PASS <area>: Setting <https://example.net>.protocol = 's' +PASS <a>: Setting <ftp://example.net>.protocol = 'test' +PASS <area>: Setting <ftp://example.net>.protocol = 'test' FAIL <a>: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/" FAIL <area>: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/" FAIL <a>: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/"
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window_include=file-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window_include=file-expected.txt deleted file mode 100644 index 8e35997..0000000 --- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window_include=file-expected.txt +++ /dev/null
@@ -1,46 +0,0 @@ -This is a testharness.js-based test. -PASS Loading data… -FAIL <a>: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" -FAIL <area>: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" -PASS <a>: Setting <file:///test>.protocol = 'https' -PASS <area>: Setting <file:///test>.protocol = 'https' -PASS <a>: Setting <file:>.protocol = 'wss' -PASS <area>: Setting <file:>.protocol = 'wss' -FAIL <a>: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "file:///S://hi/path" -FAIL <area>: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "file:///S://hi/path" -PASS <a>: Setting <file:///home/you/index.html>.username = 'me' No host means no username -PASS <area>: Setting <file:///home/you/index.html>.username = 'me' No host means no username -PASS <a>: Setting <file://test/>.username = 'test' -PASS <area>: Setting <file://test/>.username = 'test' -PASS <a>: Setting <file:///home/me/index.html>.password = 'secret' No host means no password -PASS <area>: Setting <file:///home/me/index.html>.password = 'secret' No host means no password -PASS <a>: Setting <file://test/>.password = 'test' -PASS <area>: Setting <file://test/>.password = 'test' -FAIL <a>: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/" -FAIL <area>: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/" -FAIL <a>: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL <area>: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL <a>: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x" -FAIL <area>: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS <a>: Setting <file://y/>.hostname = 'x:123' -PASS <area>: Setting <file://y/>.hostname = 'x:123' -FAIL <a>: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL <area>: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL <a>: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x" -FAIL <area>: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS <a>: Setting <file://test/>.port = '12' -PASS <area>: Setting <file://test/>.port = '12' -FAIL <a>: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/" -FAIL <area>: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/" -PASS <a>: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased -PASS <area>: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased -PASS <a>: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes -PASS <area>: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes -FAIL <a>: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes assert_equals: expected "file://////" but got "file:///" -FAIL <area>: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes assert_equals: expected "file://////" but got "file:///" -FAIL <a>: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes assert_equals: expected "file://///" but got "file:///" -FAIL <area>: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes assert_equals: expected "file://///" but got "file:///" -PASS <a>: Setting <file:///var/log/system.log>.href = 'http://0300.168.0xF0' -PASS <area>: Setting <file:///var/log/system.log>.href = 'http://0300.168.0xF0' -Harness: the test ran to completion. -
diff --git "a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt" "b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt" index fb96c231..10060d7 100644 --- "a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt" +++ "b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker_exclude=\050file_javascript_mailto\051-expected.txt"
@@ -1,21 +1,21 @@ This is a testharness.js-based test. -Found 222 tests; 138 PASS, 84 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 222 tests; 141 PASS, 81 FAIL, 0 TIMEOUT, 0 NOTRUN. PASS Loading data… FAIL URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. assert_equals: expected "a://example.net" but got "file:///A://example.net" -FAIL URL: Setting <a://example.net>.protocol = 'b' assert_equals: expected "b://example.net" but got "file:///B:///A://example.net" -FAIL URL: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased assert_equals: expected "b://example.net" but got "file:///B:///A://example.net" +FAIL URL: Setting <a://example.net>.protocol = 'b' assert_equals: expected "b://example.net" but got "file:///A://example.net" +FAIL URL: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased assert_equals: expected "b://example.net" but got "file:///A://example.net" FAIL URL: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL URL: Setting <a://example.net>.protocol = '0b' No leading digit assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL URL: Setting <a://example.net>.protocol = '+b' No leading punctuation assert_equals: expected "a://example.net" but got "file:///A://example.net" -FAIL URL: Setting <a://example.net>.protocol = 'bC0+-.' assert_equals: expected "bc0+-.://example.net" but got "bc0+-.:///A://example.net" +FAIL URL: Setting <a://example.net>.protocol = 'bC0+-.' assert_equals: expected "bc0+-.://example.net" but got "file:///A://example.net" FAIL URL: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL URL: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected assert_equals: expected "a://example.net" but got "file:///A://example.net" PASS URL: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file PASS URL: Setting <https://example.net:1234>.protocol = 'file' PASS URL: Setting <wss://x:x@example.net:1234>.protocol = 'file' -FAIL URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "file:///B://example.net/" -FAIL URL: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "file:///S://example.net/" -FAIL URL: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/" +PASS URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special +PASS URL: Setting <https://example.net>.protocol = 's' +PASS URL: Setting <ftp://example.net>.protocol = 'test' FAIL URL: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
diff --git "a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt" "b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt" index fb96c231..10060d7 100644 --- "a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt" +++ "b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any_exclude=\050file_javascript_mailto\051-expected.txt"
@@ -1,21 +1,21 @@ This is a testharness.js-based test. -Found 222 tests; 138 PASS, 84 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 222 tests; 141 PASS, 81 FAIL, 0 TIMEOUT, 0 NOTRUN. PASS Loading data… FAIL URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. assert_equals: expected "a://example.net" but got "file:///A://example.net" -FAIL URL: Setting <a://example.net>.protocol = 'b' assert_equals: expected "b://example.net" but got "file:///B:///A://example.net" -FAIL URL: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased assert_equals: expected "b://example.net" but got "file:///B:///A://example.net" +FAIL URL: Setting <a://example.net>.protocol = 'b' assert_equals: expected "b://example.net" but got "file:///A://example.net" +FAIL URL: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased assert_equals: expected "b://example.net" but got "file:///A://example.net" FAIL URL: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL URL: Setting <a://example.net>.protocol = '0b' No leading digit assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL URL: Setting <a://example.net>.protocol = '+b' No leading punctuation assert_equals: expected "a://example.net" but got "file:///A://example.net" -FAIL URL: Setting <a://example.net>.protocol = 'bC0+-.' assert_equals: expected "bc0+-.://example.net" but got "bc0+-.:///A://example.net" +FAIL URL: Setting <a://example.net>.protocol = 'bC0+-.' assert_equals: expected "bc0+-.://example.net" but got "file:///A://example.net" FAIL URL: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable assert_equals: expected "a://example.net" but got "file:///A://example.net" FAIL URL: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected assert_equals: expected "a://example.net" but got "file:///A://example.net" PASS URL: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file PASS URL: Setting <https://example.net:1234>.protocol = 'file' PASS URL: Setting <wss://x:x@example.net:1234>.protocol = 'file' -FAIL URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "file:///B://example.net/" -FAIL URL: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "file:///S://example.net/" -FAIL URL: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/" +PASS URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special +PASS URL: Setting <https://example.net>.protocol = 's' +PASS URL: Setting <ftp://example.net>.protocol = 'test' FAIL URL: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/" FAIL URL: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any_include=file-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any_include=file-expected.txt deleted file mode 100644 index 3c1b7ea..0000000 --- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any_include=file-expected.txt +++ /dev/null
@@ -1,25 +0,0 @@ -This is a testharness.js-based test. -PASS Loading data… -FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/" -PASS URL: Setting <file:///test>.protocol = 'https' -PASS URL: Setting <file:>.protocol = 'wss' -FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "file:///S://hi/path" -PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username -PASS URL: Setting <file://test/>.username = 'test' -PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password -PASS URL: Setting <file://test/>.password = 'test' -FAIL URL: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/" -FAIL URL: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL URL: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS URL: Setting <file://y/>.hostname = 'x:123' -FAIL URL: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/" -FAIL URL: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x" -PASS URL: Setting <file://test/>.port = '12' -FAIL URL: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/" -PASS URL: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased -PASS URL: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes -PASS URL: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes -PASS URL: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes -PASS URL: Setting <file:///var/log/system.log>.href = 'http://0300.168.0xF0' -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/platform/win/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt b/third_party/blink/web_tests/platform/win/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt index d1da469..e7aae84 100644 --- a/third_party/blink/web_tests/platform/win/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt +++ b/third_party/blink/web_tests/platform/win/fast/dom/HTMLAnchorElement/set-href-attribute-protocol-expected.txt
@@ -3,25 +3,25 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". Basic test -PASS a.href is 'http-foo://www.mydomain.com/path/' +PASS a.href is 'https://www.mydomain.com/path/' Set a protocol that contains ':' PASS a.href is 'http://www.mydomain.com/path/' Set a protocol that contains invalid characters PASS a.href is 'https://www.mydomain.com/path/' Set a protocol to a URL with invalid host name -FAIL a.href should be foo:^^. Was foo:///H:/%5E%5E. +FAIL a.href should be foo:^^. Was file:///H:/%5E%5E. Set a protocol that starts with ':' PASS a.href is 'https://www.mydomain.com/path/' Set protocol to null -PASS a.href is 'null://www.mydomain.com/path/' +PASS a.href is 'https://www.mydomain.com/path/' Set protocol to empty string PASS a.href is 'https://www.mydomain.com/path/' Set protocol to http on malformed URL PASS a.href is 'http:/??bar' Set protocol to a URL which points to a local file -FAIL a.href should be f-oo:path. Was f-oo:///C:/path. +FAIL a.href should be f-oo:path. Was file:///C:/path. Set protocol to undefined -PASS a.href is 'undefined://www.mydomain.com/path/' +PASS a.href is 'https://www.mydomain.com/path/' PASS successfullyParsed is true TEST COMPLETE
diff --git a/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt b/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt index 4e7842c..738cee5 100644 --- a/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt +++ b/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
@@ -969,6 +969,7 @@ property src property text property type +html element search html element section html element select property add
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-fonts/default-monospace-family-mac.html.ini b/third_party/blink/web_tests/wpt_internal/css/css-fonts/default-monospace-family-mac.html.ini new file mode 100644 index 0000000..90e355e --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/css/css-fonts/default-monospace-family-mac.html.ini
@@ -0,0 +1,5 @@ +[default-monospace-family-mac.html] + expected: + if not debug and (product == "content_shell") and (os == "win"): FAIL + if not debug and (product == "content_shell") and (os == "linux"): FAIL + if not debug and (product == "chrome"): FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/geolocation-api/watchPosition-page-visibility.https.html.ini b/third_party/blink/web_tests/wpt_internal/geolocation-api/watchPosition-page-visibility.https.html.ini index 2c291979..4b29a03 100644 --- a/third_party/blink/web_tests/wpt_internal/geolocation-api/watchPosition-page-visibility.https.html.ini +++ b/third_party/blink/web_tests/wpt_internal/geolocation-api/watchPosition-page-visibility.https.html.ini
@@ -1,8 +1,9 @@ [watchPosition-page-visibility.https.html] expected: - if (product == "content_shell") and (os == "win") and (port == "win11"): [TIMEOUT, OK] + if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): [TIMEOUT, OK] + if (product == "content_shell") and (os == "win") and (port == "win11"): TIMEOUT ERROR [Tests that watchPosition does not report position changes when the page is not visible.] expected: - if (product == "content_shell") and (os == "win") and (port == "win11"): PASS + if (product == "content_shell") and (os == "win"): PASS TIMEOUT
diff --git a/third_party/blink/web_tests/wpt_internal/ukm/ukm-get-metrics.html.ini b/third_party/blink/web_tests/wpt_internal/ukm/ukm-get-metrics.html.ini index 1154534..243f497 100644 --- a/third_party/blink/web_tests/wpt_internal/ukm/ukm-get-metrics.html.ini +++ b/third_party/blink/web_tests/wpt_internal/ukm/ukm-get-metrics.html.ini
@@ -1,7 +1,8 @@ [ukm-get-metrics.html] expected: - if (product == "content_shell") and (os == "linux") and (flag_specific == ""): TIMEOUT + if (product == "content_shell") and (os == "mac") and (port == "mac11"): [TIMEOUT, OK] if (product == "content_shell") and (os == "mac") and (port == "mac12"): TIMEOUT + if (product == "content_shell") and (os == "linux") and (flag_specific == ""): TIMEOUT [UKM recorder should receive the Blink.UpdateTime entry] expected: if product == "chrome": FAIL
diff --git a/third_party/boringssl/BUILD.generated.gni b/third_party/boringssl/BUILD.generated.gni index b23eb6a..19431b6d 100644 --- a/third_party/boringssl/BUILD.generated.gni +++ b/third_party/boringssl/BUILD.generated.gni
@@ -90,7 +90,6 @@ "src/crypto/cpu_aarch64_openbsd.c", "src/crypto/cpu_aarch64_sysreg.c", "src/crypto/cpu_aarch64_win.c", - "src/crypto/cpu_arm.c", "src/crypto/cpu_arm_freebsd.c", "src/crypto/cpu_arm_linux.c", "src/crypto/cpu_arm_linux.h",
diff --git a/third_party/mediapipe/README.chromium b/third_party/mediapipe/README.chromium index b07cfd5..ddc4c8a 100644 --- a/third_party/mediapipe/README.chromium +++ b/third_party/mediapipe/README.chromium
@@ -1,8 +1,8 @@ Name: MediaPipe Short Name: mediapipe URL: https://github.com/google/mediapipe -Version: 723e91cec10ecd50d05427ef09c735addb709e6f -Date: 2023/07/14 +Version: 13bb65db960ff1934c18a1178a225b07148473ac +Date: 2023/08/16 License: Apache 2.0 License File: LICENSE Security Critical: Yes
diff --git a/third_party/mediapipe/src/WORKSPACE b/third_party/mediapipe/src/WORKSPACE index a1ec2ab..eae8af4 100644 --- a/third_party/mediapipe/src/WORKSPACE +++ b/third_party/mediapipe/src/WORKSPACE
@@ -157,19 +157,19 @@ # 2020-08-21 http_archive( name = "com_github_glog_glog", - strip_prefix = "glog-0.6.0", - sha256 = "8a83bf982f37bb70825df71a9709fa90ea9f4447fb3c099e1d720a439d88bad6", + strip_prefix = "glog-3a0d4d22c5ae0b9a2216988411cfa6bf860cc372", + sha256 = "170d08f80210b82d95563f4723a15095eff1aad1863000e8eeb569c96a98fefb", urls = [ - "https://github.com/google/glog/archive/v0.6.0.tar.gz", + "https://github.com/google/glog/archive/3a0d4d22c5ae0b9a2216988411cfa6bf860cc372.zip", ], ) http_archive( name = "com_github_glog_glog_no_gflags", - strip_prefix = "glog-0.6.0", - sha256 = "8a83bf982f37bb70825df71a9709fa90ea9f4447fb3c099e1d720a439d88bad6", + strip_prefix = "glog-3a0d4d22c5ae0b9a2216988411cfa6bf860cc372", + sha256 = "170d08f80210b82d95563f4723a15095eff1aad1863000e8eeb569c96a98fefb", build_file = "@//third_party:glog_no_gflags.BUILD", urls = [ - "https://github.com/google/glog/archive/v0.6.0.tar.gz", + "https://github.com/google/glog/archive/3a0d4d22c5ae0b9a2216988411cfa6bf860cc372.zip", ], patches = [ "@//third_party:com_github_glog_glog.diff",
diff --git a/third_party/mediapipe/src/mediapipe/BUILD b/third_party/mediapipe/src/mediapipe/BUILD index 41443c4..fd0cbab3 100644 --- a/third_party/mediapipe/src/mediapipe/BUILD +++ b/third_party/mediapipe/src/mediapipe/BUILD
@@ -68,108 +68,30 @@ visibility = ["//visibility:public"], ) -# Generic MacOS. -config_setting( +# Note: this cannot just match "apple_platform_type": "macos" because that option +# defaults to "macos" even when building on Linux! +alias( name = "macos", - constraint_values = [ - "@platforms//os:macos", - ], + actual = select({ + ":macos_i386": ":macos_i386", + ":macos_x86_64": ":macos_x86_64", + ":macos_arm64": ":macos_arm64", + "//conditions:default": ":macos_i386", # Arbitrarily chosen from above. + }), visibility = ["//visibility:public"], ) -# MacOS x86 64-bit. -config_setting( - name = "macos_x86_64", - constraint_values = [ - "@platforms//os:macos", - "@platforms//cpu:x86_64", - ], - visibility = ["//visibility:public"], -) - -# MacOS ARM64. -config_setting( - name = "macos_arm64", - constraint_values = [ - "@platforms//os:macos", - "@platforms//cpu:arm64", - ], - visibility = ["//visibility:public"], -) - -# Generic iOS. +# Note: this also matches on crosstool_top so that it does not produce ambiguous +# selectors when used together with "android". config_setting( name = "ios", - constraint_values = [ - "@platforms//os:ios", - ], + values = { + "crosstool_top": "@bazel_tools//tools/cpp:toolchain", + "apple_platform_type": "ios", + }, visibility = ["//visibility:public"], ) -# iOS device ARM32. -config_setting( - name = "ios_armv7", - constraint_values = [ - "@platforms//os:ios", - "@platforms//cpu:arm", - ], - visibility = ["//visibility:public"], -) - -# iOS device ARM64. -config_setting( - name = "ios_arm64", - constraint_values = [ - "@platforms//os:ios", - "@platforms//cpu:arm64", - ], - visibility = ["//visibility:public"], -) - -# iOS device ARM64E. -config_setting( - name = "ios_arm64e", - constraint_values = [ - "@platforms//os:ios", - "@platforms//cpu:arm64e", - ], - visibility = ["//visibility:public"], -) - -# iOS simulator x86 32-bit. -config_setting( - name = "ios_i386", - constraint_values = [ - "@platforms//os:ios", - "@platforms//cpu:x86_32", - "@build_bazel_apple_support//constraints:simulator", - ], - visibility = ["//visibility:public"], -) - -# iOS simulator x86 64-bit. -config_setting( - name = "ios_x86_64", - constraint_values = [ - "@platforms//os:ios", - "@platforms//cpu:x86_64", - "@build_bazel_apple_support//constraints:simulator", - ], - visibility = ["//visibility:public"], -) - -# iOS simulator ARM64. -config_setting( - name = "ios_sim_arm64", - constraint_values = [ - "@platforms//os:ios", - "@platforms//cpu:arm64", - "@build_bazel_apple_support//constraints:simulator", - ], - visibility = ["//visibility:public"], -) - -# Generic Apple. alias( name = "apple", actual = select({ @@ -181,6 +103,49 @@ ) config_setting( + name = "macos_i386", + values = { + "apple_platform_type": "macos", + "cpu": "darwin", + }, + visibility = ["//visibility:public"], +) + +config_setting( + name = "macos_x86_64", + values = { + "apple_platform_type": "macos", + "cpu": "darwin_x86_64", + }, + visibility = ["//visibility:public"], +) + +config_setting( + name = "macos_arm64", + values = { + "apple_platform_type": "macos", + "cpu": "darwin_arm64", + }, + visibility = ["//visibility:public"], +) + +[ + config_setting( + name = arch, + values = {"cpu": arch}, + visibility = ["//visibility:public"], + ) + for arch in [ + "ios_i386", + "ios_x86_64", + "ios_armv7", + "ios_arm64", + "ios_arm64e", + "ios_sim_arm64", + ] +] + +config_setting( name = "windows", values = {"cpu": "x64_windows"}, )
diff --git a/third_party/mediapipe/src/mediapipe/calculators/core/BUILD b/third_party/mediapipe/src/mediapipe/calculators/core/BUILD index 99a63f63..7c5dfe8 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/core/BUILD +++ b/third_party/mediapipe/src/mediapipe/calculators/core/BUILD
@@ -381,17 +381,6 @@ alwayslink = 1, ) -cc_library( - name = "clip_detection_vector_size_calculator", - srcs = ["clip_detection_vector_size_calculator.cc"], - deps = [ - ":clip_vector_size_calculator", - "//mediapipe/framework:calculator_framework", - "//mediapipe/framework/formats:detection_cc_proto", - ], - alwayslink = 1, -) - cc_test( name = "clip_vector_size_calculator_test", srcs = ["clip_vector_size_calculator_test.cc"],
diff --git a/third_party/mediapipe/src/mediapipe/calculators/core/clip_detection_vector_size_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/core/clip_detection_vector_size_calculator.cc deleted file mode 100644 index 55bcf2fe..0000000 --- a/third_party/mediapipe/src/mediapipe/calculators/core/clip_detection_vector_size_calculator.cc +++ /dev/null
@@ -1,26 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "mediapipe/calculators/core/clip_vector_size_calculator.h" -#include "mediapipe/framework/formats/detection.pb.h" - -namespace mediapipe { - -typedef ClipVectorSizeCalculator<::mediapipe::Detection> - ClipDetectionVectorSizeCalculator; -REGISTER_CALCULATOR(ClipDetectionVectorSizeCalculator); - -} // namespace mediapipe
diff --git a/third_party/mediapipe/src/mediapipe/calculators/core/end_loop_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/core/end_loop_calculator.cc index 752580c..94f7ee22 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/core/end_loop_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/core/end_loop_calculator.cc
@@ -14,6 +14,8 @@ #include "mediapipe/calculators/core/end_loop_calculator.h" +#include <array> +#include <utility> #include <vector> #include "mediapipe/framework/formats/classification.pb.h" @@ -84,4 +86,8 @@ EndLoopAffineMatrixCalculator; REGISTER_CALCULATOR(EndLoopAffineMatrixCalculator); +typedef EndLoopCalculator<std::vector<std::pair<int, int>>> + EndLoopImageSizeCalculator; +REGISTER_CALCULATOR(EndLoopImageSizeCalculator); + } // namespace mediapipe
diff --git a/third_party/mediapipe/src/mediapipe/calculators/core/packet_thinner_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/core/packet_thinner_calculator.cc index ecb6ca57..4092dca1 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/core/packet_thinner_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/core/packet_thinner_calculator.cc
@@ -17,7 +17,6 @@ #include <cmath> // for ceil #include <memory> -#include "absl/log/absl_check.h" #include "mediapipe/calculators/core/packet_thinner_calculator.pb.h" #include "mediapipe/framework/calculator_context.h" #include "mediapipe/framework/calculator_framework.h" @@ -26,6 +25,7 @@ #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/tool/options_util.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -161,7 +161,7 @@ thinner_type_ = options.thinner_type(); // This check enables us to assume only two thinner types exist in Process() - CHECK(thinner_type_ == PacketThinnerCalculatorOptions::ASYNC || + ABSL_CHECK(thinner_type_ == PacketThinnerCalculatorOptions::ASYNC || thinner_type_ == PacketThinnerCalculatorOptions::SYNC) << "Unsupported thinner type."; @@ -178,8 +178,7 @@ } else { period_ = TimestampDiff(options.period()); } - ABSL_CHECK_LT(TimestampDiff(0), period_) - << "Specified period must be positive."; + ABSL_CHECK_LT(TimestampDiff(0), period_) << "Specified period must be positive."; if (options.has_start_time()) { start_time_ = Timestamp(options.start_time());
diff --git a/third_party/mediapipe/src/mediapipe/calculators/image/bilateral_filter_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/image/bilateral_filter_calculator.cc index 5eac351..a89452b3 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/image/bilateral_filter_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/image/bilateral_filter_calculator.cc
@@ -15,7 +15,6 @@ #include <memory> #include <string> -#include "absl/log/absl_check.h" #include "absl/strings/str_replace.h" #include "mediapipe/calculators/image/bilateral_filter_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" @@ -33,6 +32,7 @@ #include "mediapipe/gpu/gl_calculator_helper.h" #include "mediapipe/gpu/gl_simple_shaders.h" #include "mediapipe/gpu/shader_util.h" +#include "absl/log/absl_check.h" #endif // !MEDIAPIPE_DISABLE_GPU namespace mediapipe { @@ -113,7 +113,7 @@ REGISTER_CALCULATOR(BilateralFilterCalculator); absl::Status BilateralFilterCalculator::GetContract(CalculatorContract* cc) { - ABSL_CHECK_GE(cc->Inputs().NumEntries(), 1); + RET_CHECK_GE(cc->Inputs().NumEntries(), 1); if (cc->Inputs().HasTag(kInputFrameTag) && cc->Inputs().HasTag(kInputFrameTagGpu)) {
diff --git a/third_party/mediapipe/src/mediapipe/calculators/image/color_convert_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/image/color_convert_calculator.cc index 4781f1e..bb17c16 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/image/color_convert_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/image/color_convert_calculator.cc
@@ -21,12 +21,13 @@ #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/source_location.h" #include "mediapipe/framework/port/status_builder.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace { void SetColorChannel(int channel, uint8 value, cv::Mat* mat) { - CHECK(mat->depth() == CV_8U); - CHECK(channel < mat->channels()); + ABSL_CHECK(mat->depth() == CV_8U); + ABSL_CHECK(channel < mat->channels()); const int step = mat->channels(); for (int r = 0; r < mat->rows; ++r) { uint8* row_ptr = mat->ptr<uint8>(r);
diff --git a/third_party/mediapipe/src/mediapipe/calculators/image/opencv_image_encoder_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/image/opencv_image_encoder_calculator.cc index 0308b9b8..a80a221f 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/image/opencv_image_encoder_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/image/opencv_image_encoder_calculator.cc
@@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "absl/log/absl_check.h" #include "mediapipe/calculators/image/opencv_image_encoder_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/formats/image_frame_opencv.h" @@ -20,6 +19,7 @@ #include "mediapipe/framework/port/opencv_imgproc_inc.h" #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/port/status_builder.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/calculators/image/scale_image_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/image/scale_image_calculator.cc index 128aa42..af4c61d 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/image/scale_image_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/image/scale_image_calculator.cc
@@ -18,7 +18,6 @@ #include <memory> #include <string> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "absl/strings/substitute.h" #include "libyuv/scale.h" @@ -38,6 +37,7 @@ #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/status.h" #include "mediapipe/util/image_frame_util.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/calculators/image/scale_image_utils.cc b/third_party/mediapipe/src/mediapipe/calculators/image/scale_image_utils.cc index e119a87..540f782 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/image/scale_image_utils.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/image/scale_image_utils.cc
@@ -18,11 +18,11 @@ #include <string> -#include "absl/log/absl_check.h" #include "absl/strings/str_split.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/status.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace scale_image { @@ -41,10 +41,10 @@ const std::string& max_aspect_ratio, // int* crop_width, int* crop_height, // int* col_start, int* row_start) { - CHECK(crop_width); - CHECK(crop_height); - CHECK(col_start); - CHECK(row_start); + ABSL_CHECK(crop_width); + ABSL_CHECK(crop_height); + ABSL_CHECK(col_start); + ABSL_CHECK(row_start); double min_aspect_ratio_q = 0.0; double max_aspect_ratio_q = 0.0; @@ -97,8 +97,8 @@ bool preserve_aspect_ratio, // int scale_to_multiple_of, // int* output_width, int* output_height) { - CHECK(output_width); - CHECK(output_height); + ABSL_CHECK(output_width); + ABSL_CHECK(output_height); if (target_max_area > 0 && input_width * input_height > target_max_area) { preserve_aspect_ratio = true;
diff --git a/third_party/mediapipe/src/mediapipe/calculators/image/segmentation_smoothing_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/image/segmentation_smoothing_calculator.cc index 9b4a262..db0d3832 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/image/segmentation_smoothing_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/image/segmentation_smoothing_calculator.cc
@@ -32,7 +32,6 @@ #endif // !MEDIAPIPE_DISABLE_GPU #if !MEDIAPIPE_DISABLE_OPENCV -#include "absl/log/absl_check.h" #include "mediapipe/framework/formats/image_frame_opencv.h" #include "mediapipe/framework/formats/image_opencv.h" #include "mediapipe/framework/port/opencv_core_inc.h" @@ -111,7 +110,7 @@ absl::Status SegmentationSmoothingCalculator::GetContract( CalculatorContract* cc) { - ABSL_CHECK_GE(cc->Inputs().NumEntries(), 1); + RET_CHECK_GE(cc->Inputs().NumEntries(), 1); cc->Inputs().Tag(kCurrentMaskTag).Set<Image>(); cc->Inputs().Tag(kPreviousMaskTag).Set<Image>();
diff --git a/third_party/mediapipe/src/mediapipe/calculators/image/set_alpha_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/image/set_alpha_calculator.cc index 211c2840..9c381f62 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/image/set_alpha_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/image/set_alpha_calculator.cc
@@ -14,7 +14,6 @@ #include <memory> -#include "absl/log/absl_check.h" #include "mediapipe/calculators/image/set_alpha_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_options.pb.h" @@ -143,7 +142,7 @@ REGISTER_CALCULATOR(SetAlphaCalculator); absl::Status SetAlphaCalculator::GetContract(CalculatorContract* cc) { - ABSL_CHECK_GE(cc->Inputs().NumEntries(), 1); + RET_CHECK_GE(cc->Inputs().NumEntries(), 1); bool use_gpu = false;
diff --git a/third_party/mediapipe/src/mediapipe/calculators/internal/BUILD b/third_party/mediapipe/src/mediapipe/calculators/internal/BUILD index a92a2f2..a5d82e1 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/internal/BUILD +++ b/third_party/mediapipe/src/mediapipe/calculators/internal/BUILD
@@ -31,12 +31,14 @@ cc_library( name = "callback_packet_calculator", srcs = ["callback_packet_calculator.cc"], + hdrs = ["callback_packet_calculator.h"], visibility = ["//mediapipe/framework:__subpackages__"], deps = [ ":callback_packet_calculator_cc_proto", "//mediapipe/framework:calculator_base", "//mediapipe/framework:calculator_registry", "//mediapipe/framework:output_side_packet", + "@com_google_absl//absl/status", ], alwayslink = 1, )
diff --git a/third_party/mediapipe/src/mediapipe/calculators/internal/callback_packet_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/internal/callback_packet_calculator.cc index cc153483..aa86c06 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/internal/callback_packet_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/internal/callback_packet_calculator.cc
@@ -11,10 +11,12 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include "mediapipe/calculators/internal/callback_packet_calculator.h" #include <functional> #include <string> +#include "absl/status/status.h" #include "mediapipe/calculators/internal/callback_packet_calculator.pb.h" // NOLINT #include "mediapipe/framework/calculator_base.h" #include "mediapipe/framework/calculator_registry.h" @@ -39,64 +41,55 @@ *post_stream_packet = packet; } } + } // namespace -// Creates a callback which takes a packet and stores it either in a -// vector of packets or stores only the packet at PostStream timestamp. -// The kind of callback is controlled by an option. The callback is -// a std::function and is directly usable by CallbackCalculator. -// Since the options for the packet generator include a serialized pointer -// value, the resulting callback is only valid on the original machine -// while that pointer is still alive. -class CallbackPacketCalculator : public CalculatorBase { - public: - static absl::Status GetContract(CalculatorContract* cc) { - const auto& options = cc->Options<CallbackPacketCalculatorOptions>(); - switch (options.type()) { - case CallbackPacketCalculatorOptions::VECTOR_PACKET: - case CallbackPacketCalculatorOptions::POST_STREAM_PACKET: - cc->OutputSidePackets() - .Index(0) - .Set<std::function<void(const Packet&)>>(); - break; - default: - return mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC) - << "Invalid type of callback to produce."; - } - return absl::OkStatus(); - } - - absl::Status Open(CalculatorContext* cc) override { - const auto& options = cc->Options<CallbackPacketCalculatorOptions>(); - void* ptr; - if (sscanf(options.pointer().c_str(), "%p", &ptr) != 1) { +absl::Status CallbackPacketCalculator::GetContract(CalculatorContract* cc) { + const auto& options = cc->Options<CallbackPacketCalculatorOptions>(); + switch (options.type()) { + case CallbackPacketCalculatorOptions::VECTOR_PACKET: + case CallbackPacketCalculatorOptions::POST_STREAM_PACKET: + cc->OutputSidePackets() + .Index(0) + .Set<std::function<void(const Packet&)>>(); + break; + default: return mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC) - << "Stored pointer value in options is invalid."; - } - switch (options.type()) { - case CallbackPacketCalculatorOptions::VECTOR_PACKET: - cc->OutputSidePackets().Index(0).Set( - MakePacket<std::function<void(const Packet&)>>(std::bind( - &DumpToVector, reinterpret_cast<std::vector<Packet>*>(ptr), - std::placeholders::_1))); - break; - case CallbackPacketCalculatorOptions::POST_STREAM_PACKET: - cc->OutputSidePackets().Index(0).Set( - MakePacket<std::function<void(const Packet&)>>( - std::bind(&DumpPostStreamPacket, reinterpret_cast<Packet*>(ptr), - std::placeholders::_1))); - break; - default: - return mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC) - << "Invalid type to dump into."; - } - return absl::OkStatus(); + << "Invalid type of callback to produce."; } + return absl::OkStatus(); +} - absl::Status Process(CalculatorContext* cc) override { - return absl::OkStatus(); +absl::Status CallbackPacketCalculator::Open(CalculatorContext* cc) { + const auto& options = cc->Options<CallbackPacketCalculatorOptions>(); + void* ptr; + if (sscanf(options.pointer().c_str(), "%p", &ptr) != 1) { + return mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC) + << "Stored pointer value in options is invalid."; } -}; + switch (options.type()) { + case CallbackPacketCalculatorOptions::VECTOR_PACKET: + cc->OutputSidePackets().Index(0).Set( + MakePacket<std::function<void(const Packet&)>>(std::bind( + &DumpToVector, reinterpret_cast<std::vector<Packet>*>(ptr), + std::placeholders::_1))); + break; + case CallbackPacketCalculatorOptions::POST_STREAM_PACKET: + cc->OutputSidePackets().Index(0).Set( + MakePacket<std::function<void(const Packet&)>>( + std::bind(&DumpPostStreamPacket, reinterpret_cast<Packet*>(ptr), + std::placeholders::_1))); + break; + default: + return mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC) + << "Invalid type to dump into."; + } + return absl::OkStatus(); +} + +absl::Status CallbackPacketCalculator::Process(CalculatorContext* cc) { + return absl::OkStatus(); +} REGISTER_CALCULATOR(CallbackPacketCalculator);
diff --git a/third_party/mediapipe/src/mediapipe/calculators/internal/callback_packet_calculator.h b/third_party/mediapipe/src/mediapipe/calculators/internal/callback_packet_calculator.h new file mode 100644 index 0000000..e0b170e3 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/calculators/internal/callback_packet_calculator.h
@@ -0,0 +1,39 @@ +// Copyright 2023 The MediaPipe Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIAPIPE_CALCULATORS_INTERNAL_CALLBACK_PACKET_CALCULATOR_H_ +#define MEDIAPIPE_CALCULATORS_INTERNAL_CALLBACK_PACKET_CALCULATOR_H_ + +#include "absl/status/status.h" +#include "mediapipe/framework/calculator_base.h" + +namespace mediapipe { + +// Creates a callback which takes a packet and stores it either in a +// vector of packets or stores only the packet at PostStream timestamp. +// The kind of callback is controlled by an option. The callback is +// a std::function and is directly usable by CallbackCalculator. +// Since the options for the packet generator include a serialized pointer +// value, the resulting callback is only valid on the original machine +// while that pointer is still alive. +class CallbackPacketCalculator : public CalculatorBase { + public: + static absl::Status GetContract(CalculatorContract* cc); + absl::Status Open(CalculatorContext* cc) override; + absl::Status Process(CalculatorContext* cc) override; +}; + +} // namespace mediapipe + +#endif // MEDIAPIPE_CALCULATORS_INTERNAL_CALLBACK_PACKET_CALCULATOR_H_
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensor/BUILD b/third_party/mediapipe/src/mediapipe/calculators/tensor/BUILD index a3e61c0..c3397b8 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensor/BUILD +++ b/third_party/mediapipe/src/mediapipe/calculators/tensor/BUILD
@@ -620,6 +620,7 @@ deps = [ "//mediapipe/framework:calculator_options_proto", "//mediapipe/framework:calculator_proto", + "//mediapipe/gpu:gpu_origin_proto", ], ) @@ -649,7 +650,12 @@ "//mediapipe/framework/formats:matrix", "//mediapipe/framework/formats:tensor", "//mediapipe/framework/port:ret_check", + "//mediapipe/framework/port:status", + "//mediapipe/framework/port:statusor", + "//mediapipe/gpu:gpu_buffer_format", + "//mediapipe/gpu:gpu_origin_cc_proto", "//mediapipe/util:resource_util", + "@com_google_absl//absl/strings:str_format", ] + select({ "//mediapipe/gpu:disable_gpu": [], "//conditions:default": ["tensor_converter_calculator_gpu_deps"], @@ -699,6 +705,7 @@ "//mediapipe/framework/formats:tensor", "//mediapipe/framework/port:gtest_main", "//mediapipe/framework/port:integral_types", + "//mediapipe/framework/port:opencv_core", "//mediapipe/framework/port:parse_text_proto", "//mediapipe/framework/tool:validate_type", "@com_google_absl//absl/memory",
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensor/audio_to_tensor_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tensor/audio_to_tensor_calculator.cc index e8c9a6c..c5451df 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensor/audio_to_tensor_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tensor/audio_to_tensor_calculator.cc
@@ -20,7 +20,6 @@ #include <utility> #include <vector> -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/status/statusor.h" @@ -38,6 +37,7 @@ #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/util/time_series_util.h" #include "pffft.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace api2 { @@ -349,7 +349,7 @@ return absl::InvalidArgumentError( "The audio data should be stored in column-major."); } - CHECK(channels_match || mono_output); + ABSL_CHECK(channels_match || mono_output); const Matrix& input = channels_match ? input_frame // Mono mixdown. : input_frame.colwise().mean();
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensor/tensor_converter_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tensor/tensor_converter_calculator.cc index a9606e65..5cef184c 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensor/tensor_converter_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tensor/tensor_converter_calculator.cc
@@ -12,10 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <cstdint> #include <string> #include <vector> -#include "absl/log/absl_check.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_format.h" #include "mediapipe/calculators/tensor/tensor_converter_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/formats/image_frame.h" @@ -23,7 +26,8 @@ #include "mediapipe/framework/formats/tensor.h" #include "mediapipe/framework/port.h" #include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/util/resource_util.h" +#include "mediapipe/gpu/gpu_buffer_format.h" +#include "mediapipe/gpu/gpu_origin.pb.h" #if !MEDIAPIPE_DISABLE_GPU #include "mediapipe/gpu/gpu_buffer.h" @@ -39,17 +43,42 @@ #if MEDIAPIPE_OPENGL_ES_VERSION < MEDIAPIPE_OPENGL_ES_31 #include "mediapipe/gpu/gl_simple_shaders.h" #include "mediapipe/gpu/shader_util.h" +#include "absl/log/absl_check.h" #endif // MEDIAPIPE_OPENGL_ES_VERSION < MEDIAPIPE_OPENGL_ES_31 #endif // MEDIAPIPE_METAL_ENABLED #endif // !MEDIAPIPE_DISABLE_GPU namespace { + constexpr int kWorkgroupSize = 8; // Block size for GPU shader. // Commonly used to compute the number of blocks to launch in a kernel. int NumGroups(const int size, const int group_size) { // NOLINT return (size + group_size - 1) / group_size; } +absl::StatusOr<bool> ShouldFlipVertically( + const mediapipe::TensorConverterCalculatorOptions& options) { + if (!options.has_gpu_origin()) { + return options.flip_vertically(); + } + + switch (options.gpu_origin()) { + case mediapipe::GpuOrigin::TOP_LEFT: + return false; + case mediapipe::GpuOrigin::DEFAULT: + case mediapipe::GpuOrigin::CONVENTIONAL: + // TOP_LEFT on Metal, BOTTOM_LEFT on OpenGL. +#ifdef __APPLE__ + return false; +#else + return true; +#endif + } + + return absl::InvalidArgumentError( + absl::StrFormat("Unhandled GPU origin %i", options.gpu_origin())); +} + typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> RowMajorMatrixXf; typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor> @@ -59,6 +88,7 @@ constexpr char kGpuBufferTag[] = "IMAGE_GPU"; constexpr char kTensorsTag[] = "TENSORS"; constexpr char kMatrixTag[] = "MATRIX"; + } // namespace namespace mediapipe { @@ -379,16 +409,27 @@ // Get input image sizes. const auto& input = cc->Inputs().Tag(kGpuBufferTag).Get<mediapipe::GpuBuffer>(); - mediapipe::ImageFormat::Format format = - mediapipe::ImageFormatForGpuBufferFormat(input.format()); + mediapipe::GpuBufferFormat format = input.format(); const bool include_alpha = (max_num_channels_ == 4); const bool single_channel = (max_num_channels_ == 1); - if (!(format == mediapipe::ImageFormat::GRAY8 || - format == mediapipe::ImageFormat::SRGB || - format == mediapipe::ImageFormat::SRGBA)) - RET_CHECK_FAIL() << "Unsupported GPU input format."; - if (include_alpha && (format != mediapipe::ImageFormat::SRGBA)) - RET_CHECK_FAIL() << "Num input channels is less than desired output."; + + RET_CHECK(format == mediapipe::GpuBufferFormat::kBGRA32 || + format == mediapipe::GpuBufferFormat::kRGB24 || + format == mediapipe::GpuBufferFormat::kRGBA32 || + format == mediapipe::GpuBufferFormat::kRGBAFloat128 || + format == mediapipe::GpuBufferFormat::kRGBAHalf64 || + format == mediapipe::GpuBufferFormat::kGrayFloat32 || + format == mediapipe::GpuBufferFormat::kGrayHalf16 || + format == mediapipe::GpuBufferFormat::kOneComponent8) + << "Unsupported GPU input format: " << static_cast<uint32_t>(format); + if (include_alpha) { + RET_CHECK(format == mediapipe::GpuBufferFormat::kBGRA32 || + format == mediapipe::GpuBufferFormat::kRGBA32 || + format == mediapipe::GpuBufferFormat::kRGBAFloat128 || + format == mediapipe::GpuBufferFormat::kRGBAHalf64) + << "Num input channels is less than desired output, input format: " + << static_cast<uint32_t>(format); + } #if MEDIAPIPE_METAL_ENABLED id<MTLDevice> device = gpu_helper_.mtlDevice; @@ -594,7 +635,7 @@ } // Get y-flip mode. - flip_vertically_ = options.flip_vertically(); + ASSIGN_OR_RETURN(flip_vertically_, ShouldFlipVertically(options)); // Get row_major_matrix mode. row_major_matrix_ = options.row_major_matrix();
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensor/tensor_converter_calculator.proto b/third_party/mediapipe/src/mediapipe/calculators/tensor/tensor_converter_calculator.proto index 97c2154..194dd417 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensor/tensor_converter_calculator.proto +++ b/third_party/mediapipe/src/mediapipe/calculators/tensor/tensor_converter_calculator.proto
@@ -3,6 +3,7 @@ package mediapipe; import "mediapipe/framework/calculator.proto"; +import "mediapipe/gpu/gpu_origin.proto"; // Full Example: // @@ -43,8 +44,14 @@ // with a coordinate system where the origin is at the bottom-left corner // (e.g., in OpenGL) whereas the ML model expects an image with a top-left // origin. + // Prefer gpu_origin over this field. optional bool flip_vertically = 2 [default = false]; + // Determines when the input image should be flipped vertically. + // See GpuOrigin.Mode for more information. + // If unset, falls back to flip_vertically for backwards compatibility. + optional GpuOrigin.Mode gpu_origin = 10; + // Controls how many channels of the input image get passed through to the // tensor. Valid values are 1,3,4 only. Ignored for iOS GPU. optional int32 max_num_channels = 3 [default = 3];
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensor/tensors_to_detections_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tensor/tensors_to_detections_calculator.cc index 3e19ae5b..eafad6e8 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensor/tensors_to_detections_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tensor/tensors_to_detections_calculator.cc
@@ -15,7 +15,6 @@ #include <unordered_map> #include <vector> -#include "absl/log/absl_check.h" #include "absl/strings/str_format.h" #include "absl/types/span.h" #include "mediapipe/calculators/tensor/tensors_to_detections_calculator.pb.h" @@ -714,8 +713,8 @@ // Check if the output size is equal to the requested boxes and keypoints. ABSL_CHECK_EQ(options_.num_keypoints() * options_.num_values_per_keypoint() + - kNumCoordsPerBox, - num_coords_); + kNumCoordsPerBox, + num_coords_); if (kSideInIgnoreClasses(cc).IsConnected()) { RET_CHECK(!kSideInIgnoreClasses(cc).IsEmpty()); @@ -1156,11 +1155,10 @@ // TODO support better filtering. if (class_index_set_.is_allowlist) { ABSL_CHECK_EQ(class_index_set_.values.size(), - IsClassIndexAllowed(0) ? num_classes_ : num_classes_ - 1) + IsClassIndexAllowed(0) ? num_classes_ : num_classes_ - 1) << "Only all classes >= class 0 or >= class 1"; } else { - ABSL_CHECK_EQ(class_index_set_.values.size(), - IsClassIndexAllowed(0) ? 0 : 1) + ABSL_CHECK_EQ(class_index_set_.values.size(), IsClassIndexAllowed(0) ? 0 : 1) << "Only ignore class 0 is allowed"; } @@ -1323,6 +1321,7 @@ const std::string score_src = absl::Substitute( R"( #include <metal_stdlib> +#include "absl/log/absl_check.h" using namespace metal; @@ -1382,11 +1381,10 @@ // TODO support better filtering. if (class_index_set_.is_allowlist) { ABSL_CHECK_EQ(class_index_set_.values.size(), - IsClassIndexAllowed(0) ? num_classes_ : num_classes_ - 1) + IsClassIndexAllowed(0) ? num_classes_ : num_classes_ - 1) << "Only all classes >= class 0 or >= class 1"; } else { - ABSL_CHECK_EQ(class_index_set_.values.size(), - IsClassIndexAllowed(0) ? 0 : 1) + ABSL_CHECK_EQ(class_index_set_.values.size(), IsClassIndexAllowed(0) ? 0 : 1) << "Only ignore class 0 is allowed"; }
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensor/tensors_to_landmarks_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tensor/tensors_to_landmarks_calculator.cc index cfee6bad..ec559eb 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensor/tensors_to_landmarks_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tensor/tensors_to_landmarks_calculator.cc
@@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "absl/log/absl_check.h" #include "mediapipe/calculators/tensor/tensors_to_landmarks_calculator.pb.h" #include "mediapipe/framework/api2/node.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/formats/landmark.pb.h" #include "mediapipe/framework/formats/tensor.h" #include "mediapipe/framework/port/ret_check.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace api2 {
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/BUILD b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/BUILD index aec657e..3744784 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/BUILD +++ b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/BUILD
@@ -366,15 +366,15 @@ name = "pack_media_sequence_calculator", srcs = ["pack_media_sequence_calculator.cc"], deps = [ + ":pack_media_sequence_calculator_cc_proto", "//mediapipe/calculators/image:opencv_image_encoder_calculator_cc_proto", - "//mediapipe/calculators/tensorflow:pack_media_sequence_calculator_cc_proto", "//mediapipe/framework:calculator_framework", + "//mediapipe/framework/formats:classification_cc_proto", "//mediapipe/framework/formats:detection_cc_proto", "//mediapipe/framework/formats:location", "//mediapipe/framework/formats:location_opencv", "//mediapipe/framework/port:opencv_imgcodecs", "//mediapipe/framework/port:ret_check", - "//mediapipe/framework/port:status", "//mediapipe/util/sequence:media_sequence", "//mediapipe/util/sequence:media_sequence_util", "@com_google_absl//absl/container:flat_hash_map", @@ -406,8 +406,13 @@ alwayslink = 1, ) -# This dependency removed tensorflow_jellyfish_deps and xprofilez_with_server because they failed -# Boq conformance test. Weigh your use case to see if this will work for you. +# This dependency removed the following 3 targets because they failed Boq conformance test: +# +# tensorflow_jellyfish_deps +# jfprof_lib +# xprofilez_with_server +# +# If you need them plz consider tensorflow_inference_calculator_no_envelope_loader. cc_library( name = "tensorflow_inference_calculator_for_boq", srcs = ["tensorflow_inference_calculator.cc"], @@ -920,21 +925,21 @@ srcs = ["pack_media_sequence_calculator_test.cc"], deps = [ ":pack_media_sequence_calculator", + ":pack_media_sequence_calculator_cc_proto", "//mediapipe/calculators/image:opencv_image_encoder_calculator_cc_proto", - "//mediapipe/calculators/tensorflow:pack_media_sequence_calculator_cc_proto", "//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_runner", "//mediapipe/framework:timestamp", + "//mediapipe/framework/formats:classification_cc_proto", "//mediapipe/framework/formats:detection_cc_proto", - "//mediapipe/framework/formats:image_frame", "//mediapipe/framework/formats:location", "//mediapipe/framework/formats:location_opencv", "//mediapipe/framework/port:gtest_main", "//mediapipe/framework/port:opencv_imgcodecs", "//mediapipe/util/sequence:media_sequence", - "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", "@org_tensorflow//tensorflow/core:protos_all_cc", ], )
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/matrix_to_tensor_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/matrix_to_tensor_calculator.cc index 32a0eb70..bc1a028 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/matrix_to_tensor_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/matrix_to_tensor_calculator.cc
@@ -22,13 +22,14 @@ #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/framework/types.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace { absl::Status FillTimeSeriesHeaderIfValid(const Packet& header_packet, TimeSeriesHeader* header) { - CHECK(header); + ABSL_CHECK(header); if (header_packet.IsEmpty()) { return absl::UnknownError("No header found."); }
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/pack_media_sequence_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/pack_media_sequence_calculator.cc index 3413644..75878b7 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/pack_media_sequence_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/pack_media_sequence_calculator.cc
@@ -17,16 +17,16 @@ #include "absl/container/flat_hash_map.h" #include "absl/strings/match.h" +#include "absl/strings/strip.h" #include "mediapipe/calculators/image/opencv_image_encoder_calculator.pb.h" #include "mediapipe/calculators/tensorflow/pack_media_sequence_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/formats/classification.pb.h" #include "mediapipe/framework/formats/detection.pb.h" #include "mediapipe/framework/formats/location.h" #include "mediapipe/framework/formats/location_opencv.h" -#include "mediapipe/framework/port/canonical_errors.h" #include "mediapipe/framework/port/opencv_imgcodecs_inc.h" #include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" #include "mediapipe/util/sequence/media_sequence.h" #include "mediapipe/util/sequence/media_sequence_util.h" #include "tensorflow/core/example/example.pb.h" @@ -36,6 +36,7 @@ const char kSequenceExampleTag[] = "SEQUENCE_EXAMPLE"; const char kImageTag[] = "IMAGE"; +const char kImageLabelPrefixTag[] = "IMAGE_LABEL_"; const char kFloatContextFeaturePrefixTag[] = "FLOAT_CONTEXT_FEATURE_"; const char kFloatFeaturePrefixTag[] = "FLOAT_FEATURE_"; const char kIntFeaturePrefixTag[] = "INT_FEATURE_"; @@ -56,7 +57,8 @@ // SequenceExample will conform to the description in media_sequence.h. // // The supported input stream tags are "IMAGE", which stores the encoded -// images from the OpenCVImageEncoderCalculator, "FORWARD_FLOW_ENCODED", which +// images from the OpenCVImageEncoderCalculator, "IMAGE_LABEL", which stores +// image labels from vector<Classification>, "FORWARD_FLOW_ENCODED", which // stores the encoded optical flow from the same calculator, "BBOX" which stores // bounding boxes from vector<Detections>, and streams with the // "FLOAT_FEATURE_${NAME}" pattern, which stores the values from vector<float>'s @@ -112,6 +114,10 @@ for (const auto& tag : cc->Inputs().GetTags()) { if (absl::StartsWith(tag, kImageTag)) { + if (absl::StartsWith(tag, kImageLabelPrefixTag)) { + cc->Inputs().Tag(tag).Set<std::vector<Classification>>(); + continue; + } std::string key = ""; if (tag != kImageTag) { int tag_length = sizeof(kImageTag) / sizeof(*kImageTag) - 1; @@ -164,8 +170,8 @@ } } - CHECK(cc->Outputs().HasTag(kSequenceExampleTag) || - cc->OutputSidePackets().HasTag(kSequenceExampleTag)) + RET_CHECK(cc->Outputs().HasTag(kSequenceExampleTag) || + cc->OutputSidePackets().HasTag(kSequenceExampleTag)) << "Neither the output stream nor the output side packet is set to " "output the sequence example."; if (cc->Outputs().HasTag(kSequenceExampleTag)) { @@ -199,6 +205,16 @@ .replace_data_instead_of_append()) { for (const auto& tag : cc->Inputs().GetTags()) { if (absl::StartsWith(tag, kImageTag)) { + if (absl::StartsWith(tag, kImageLabelPrefixTag)) { + std::string key = + std::string(absl::StripPrefix(tag, kImageLabelPrefixTag)); + mpms::ClearImageLabelString(key, sequence_.get()); + mpms::ClearImageLabelConfidence(key, sequence_.get()); + if (!key.empty() || mpms::HasImageEncoded(*sequence_)) { + mpms::ClearImageTimestamp(key, sequence_.get()); + } + continue; + } std::string key = ""; if (tag != kImageTag) { int tag_length = sizeof(kImageTag) / sizeof(*kImageTag) - 1; @@ -227,6 +243,7 @@ mpms::ClearBBoxNumRegions(key, sequence_.get()); mpms::ClearBBoxLabelString(key, sequence_.get()); mpms::ClearBBoxLabelIndex(key, sequence_.get()); + mpms::ClearBBoxLabelConfidence(key, sequence_.get()); mpms::ClearBBoxClassString(key, sequence_.get()); mpms::ClearBBoxClassIndex(key, sequence_.get()); mpms::ClearBBoxTrackString(key, sequence_.get()); @@ -343,6 +360,24 @@ if (absl::StartsWith(tag, kImageTag) && !cc->Inputs().Tag(tag).IsEmpty()) { std::string key = ""; + if (absl::StartsWith(tag, kImageLabelPrefixTag)) { + std::string key = + std::string(absl::StripPrefix(tag, kImageLabelPrefixTag)); + std::vector<std::string> labels; + std::vector<float> confidences; + for (const auto& classification : + cc->Inputs().Tag(tag).Get<std::vector<Classification>>()) { + labels.push_back(classification.label()); + confidences.push_back(classification.score()); + } + if (!key.empty() || mpms::HasImageEncoded(*sequence_)) { + mpms::AddImageTimestamp(key, cc->InputTimestamp().Value(), + sequence_.get()); + } + mpms::AddImageLabelString(key, labels, sequence_.get()); + mpms::AddImageLabelConfidence(key, confidences, sequence_.get()); + continue; + } if (tag != kImageTag) { int tag_length = sizeof(kImageTag) / sizeof(*kImageTag) - 1; if (tag[tag_length] == '_') { @@ -393,6 +428,7 @@ mpms::ClearBBoxNumRegions(prefix, sequence_.get()); mpms::ClearBBoxLabelString(prefix, sequence_.get()); mpms::ClearBBoxLabelIndex(prefix, sequence_.get()); + mpms::ClearBBoxLabelConfidence(prefix, sequence_.get()); mpms::ClearBBoxClassString(prefix, sequence_.get()); mpms::ClearBBoxClassIndex(prefix, sequence_.get()); mpms::ClearBBoxTrackString(prefix, sequence_.get()); @@ -460,6 +496,7 @@ } std::vector<Location> predicted_locations; std::vector<std::string> predicted_class_strings; + std::vector<float> predicted_class_confidences; std::vector<int> predicted_label_ids; for (auto& detection : cc->Inputs().Tag(tag).Get<std::vector<Detection>>()) { @@ -488,6 +525,9 @@ if (detection.label_id_size() > 0) { predicted_label_ids.push_back(detection.label_id(0)); } + if (detection.score_size() > 0) { + predicted_class_confidences.push_back(detection.score(0)); + } } } if (!predicted_locations.empty()) { @@ -501,6 +541,10 @@ if (!predicted_label_ids.empty()) { mpms::AddBBoxLabelIndex(key, predicted_label_ids, sequence_.get()); } + if (!predicted_class_confidences.empty()) { + mpms::AddBBoxLabelConfidence(key, predicted_class_confidences, + sequence_.get()); + } } } }
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensor_to_image_frame_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensor_to_image_frame_calculator.cc index b5a94e0..7f79fb0 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensor_to_image_frame_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensor_to_image_frame_calculator.cc
@@ -22,6 +22,7 @@ #include "mediapipe/framework/port/status_macros.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/types.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -99,7 +100,7 @@ const tf::Tensor& input_tensor = cc->Inputs().Tag(kTensor).Get<tf::Tensor>(); int32_t depth = 1; if (input_tensor.dims() != 2) { // Depth is 1 for 2D tensors. - CHECK(3 == input_tensor.dims()) + ABSL_CHECK(3 == input_tensor.dims()) << "Only 2 or 3-D Tensors can be converted to frames. Instead got: " << input_tensor.dims(); depth = input_tensor.dim_size(2);
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensor_to_matrix_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensor_to_matrix_calculator.cc index 081e0c83..2036d36 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensor_to_matrix_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensor_to_matrix_calculator.cc
@@ -24,6 +24,7 @@ #include "mediapipe/framework/port/status_macros.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/types.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -36,7 +37,7 @@ absl::Status FillTimeSeriesHeaderIfValid(const Packet& header_packet, TimeSeriesHeader* header) { - CHECK(header); + ABSL_CHECK(header); if (header_packet.IsEmpty()) { return absl::UnknownError("No header found."); } @@ -191,7 +192,7 @@ << "Tensor stream packet does not contain a Tensor."; const tf::Tensor& input_tensor = cc->Inputs().Tag(kTensor).Get<tf::Tensor>(); - CHECK(1 == input_tensor.dims() || 2 == input_tensor.dims()) + ABSL_CHECK(1 == input_tensor.dims() || 2 == input_tensor.dims()) << "Only 1-D or 2-D Tensors can be converted to matrices."; const int32_t length = input_tensor.dim_size(input_tensor.dims() - 1); const int32_t width =
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensorflow_inference_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensorflow_inference_calculator.cc index 2608b1c..731fe01 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensorflow_inference_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/tensorflow_inference_calculator.cc
@@ -42,6 +42,7 @@ #if !defined(MEDIAPIPE_MOBILE) && !defined(__APPLE__) #include "tensorflow/core/profiler/lib/traceme.h" +#include "absl/log/absl_check.h" #endif namespace tf = ::tensorflow; @@ -515,7 +516,7 @@ tf::Tensor concated; const tf::Status concat_status = tf::tensor::Concat(keyed_tensors.second, &concated); - CHECK(concat_status.ok()) << concat_status.ToString(); + ABSL_CHECK(concat_status.ok()) << concat_status.ToString(); input_tensors.emplace_back(tag_to_tensor_map_[keyed_tensors.first], concated); } @@ -597,7 +598,7 @@ std::vector<tf::Tensor> split_tensors; const tf::Status split_status = tf::tensor::Split(outputs[i], split_vector, &split_tensors); - CHECK(split_status.ok()) << split_status.ToString(); + ABSL_CHECK(split_status.ok()) << split_status.ToString(); // Loop over timestamps so that we don't copy the padding. for (int j = 0; j < inference_state->batch_timestamps_.size(); ++j) { tf::Tensor output_tensor(split_tensors[j]);
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/vector_int_to_tensor_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/vector_int_to_tensor_calculator.cc index 04c3ba1..8a8d1027 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tensorflow/vector_int_to_tensor_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tensorflow/vector_int_to_tensor_calculator.cc
@@ -15,13 +15,13 @@ // Converts a single int or vector<int> or vector<vector<int>> to 1D (or 2D) // tf::Tensor. -#include "absl/log/absl_check.h" #include "mediapipe/calculators/tensorflow/vector_int_to_tensor_calculator_options.pb.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/status.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/types.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tflite/ssd_anchors_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tflite/ssd_anchors_calculator.cc index e56765d94..12f8c582 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tflite/ssd_anchors_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tflite/ssd_anchors_calculator.cc
@@ -16,11 +16,11 @@ #include <utility> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/calculators/tflite/ssd_anchors_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/formats/object_detection/anchor.pb.h" #include "mediapipe/framework/port/ret_check.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -277,7 +277,7 @@ } ABSL_CHECK_EQ(options.feature_map_height_size(), kNumLayers); ABSL_CHECK_EQ(options.feature_map_height_size(), - options.feature_map_width_size()); + options.feature_map_width_size()); } else { ABSL_CHECK_EQ(options.strides_size(), kNumLayers); }
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_converter_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_converter_calculator.cc index 7188cbc..0727c13 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_converter_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_converter_calculator.cc
@@ -15,7 +15,6 @@ #include <string> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/calculators/tflite/tflite_converter_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/formats/image_frame.h" @@ -47,6 +46,7 @@ #include "mediapipe/gpu/MPPMetalUtil.h" #include "mediapipe/gpu/gpu_buffer.h" #include "tensorflow/lite/delegates/gpu/metal_delegate.h" +#include "absl/log/absl_check.h" #endif // MEDIAPIPE_TFLITE_METAL_INFERENCE namespace {
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_inference_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_inference_calculator.cc index 4b891332..1037f2a 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_inference_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_inference_calculator.cc
@@ -17,7 +17,6 @@ #include <string> #include <vector> -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "mediapipe/calculators/tflite/tflite_inference_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" @@ -87,6 +86,7 @@ #if defined(MEDIAPIPE_EDGE_TPU) #include "tflite/public/edgetpu.h" +#include "absl/log/absl_check.h" // Checkes whether model contains Edge TPU custom op or not. bool ContainsEdgeTpuCustomOp(const tflite::FlatBufferModel& model) { @@ -111,7 +111,7 @@ resolver->AddCustom(edgetpu::kCustomOp, edgetpu::RegisterCustomOp()); std::unique_ptr<tflite::Interpreter> interpreter; ABSL_CHECK_EQ(tflite::InterpreterBuilder(model, *resolver)(&interpreter), - kTfLiteOk); + kTfLiteOk); interpreter->SetExternalContext(kTfLiteEdgeTpuContext, edgetpu_context); return interpreter; } @@ -411,7 +411,7 @@ "Falling back to the default TFLite API."; use_advanced_gpu_api_ = false; } - CHECK(!use_advanced_gpu_api_ || gpu_inference_); + ABSL_CHECK(!use_advanced_gpu_api_ || gpu_inference_); MP_RETURN_IF_ERROR(LoadModel(cc)); @@ -803,9 +803,9 @@ const int tensor_idx = interpreter_->inputs()[i]; interpreter_->SetTensorParametersReadWrite(tensor_idx, kTfLiteFloat32, "", shape, quant); - CHECK(interpreter_->ResizeInputTensor(tensor_idx, shape) == kTfLiteOk); + ABSL_CHECK(interpreter_->ResizeInputTensor(tensor_idx, shape) == kTfLiteOk); } - CHECK(interpreter_->AllocateTensors() == kTfLiteOk); + ABSL_CHECK(interpreter_->AllocateTensors() == kTfLiteOk); } // Create and bind OpenGL buffers for outputs.
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_classification_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_classification_calculator.cc index 98ab4b1d..d64e2e9 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_classification_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_classification_calculator.cc
@@ -17,7 +17,6 @@ #include <vector> #include "absl/container/node_hash_map.h" -#include "absl/log/absl_check.h" #include "absl/strings/str_format.h" #include "absl/types/span.h" #include "mediapipe/calculators/tflite/tflite_tensors_to_classification_calculator.pb.h" @@ -31,6 +30,7 @@ #include "mediapipe/util/android/file/base/helpers.h" #else #include "mediapipe/framework/port/file_helpers.h" +#include "absl/log/absl_check.h" #endif namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_detections_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_detections_calculator.cc index 2d91f54..0a80778c 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_detections_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_detections_calculator.cc
@@ -15,7 +15,6 @@ #include <unordered_map> #include <vector> -#include "absl/log/absl_check.h" #include "absl/strings/str_format.h" #include "absl/types/span.h" #include "mediapipe/calculators/tflite/tflite_tensors_to_detections_calculator.pb.h" @@ -310,7 +309,7 @@ const float* raw_anchors = anchor_tensor->data.f; ConvertRawValuesToAnchors(raw_anchors, num_boxes_, &anchors_); } else if (side_packet_anchors_) { - CHECK(!cc->InputSidePackets().Tag("ANCHORS").IsEmpty()); + ABSL_CHECK(!cc->InputSidePackets().Tag("ANCHORS").IsEmpty()); anchors_ = cc->InputSidePackets().Tag("ANCHORS").Get<std::vector<Anchor>>(); } else { @@ -410,7 +409,7 @@ CopyBuffer(input_tensors[1], gpu_data_->raw_scores_buffer)); if (!anchors_init_) { if (side_packet_anchors_) { - CHECK(!cc->InputSidePackets().Tag("ANCHORS").IsEmpty()); + ABSL_CHECK(!cc->InputSidePackets().Tag("ANCHORS").IsEmpty()); const auto& anchors = cc->InputSidePackets().Tag("ANCHORS").Get<std::vector<Anchor>>(); std::vector<float> raw_anchors(num_boxes_ * kNumCoordsPerBox); @@ -478,7 +477,7 @@ commandBuffer:[gpu_helper_ commandBuffer]]; if (!anchors_init_) { if (side_packet_anchors_) { - CHECK(!cc->InputSidePackets().Tag("ANCHORS").IsEmpty()); + ABSL_CHECK(!cc->InputSidePackets().Tag("ANCHORS").IsEmpty()); const auto& anchors = cc->InputSidePackets().Tag("ANCHORS").Get<std::vector<Anchor>>(); std::vector<float> raw_anchors(num_boxes_ * kNumCoordsPerBox); @@ -572,8 +571,8 @@ // Check if the output size is equal to the requested boxes and keypoints. ABSL_CHECK_EQ(options_.num_keypoints() * options_.num_values_per_keypoint() + - kNumCoordsPerBox, - num_coords_); + kNumCoordsPerBox, + num_coords_); for (int i = 0; i < options_.ignore_classes_size(); ++i) { ignore_classes_.insert(options_.ignore_classes(i)); @@ -901,8 +900,7 @@ ABSL_CHECK_LT(num_classes_, max_wg_size) << "# classes must be < " << max_wg_size; // TODO support better filtering. - ABSL_CHECK_LE(ignore_classes_.size(), 1) - << "Only ignore class 0 is allowed"; + ABSL_CHECK_LE(ignore_classes_.size(), 1) << "Only ignore class 0 is allowed"; // Shader program GlShader score_shader; @@ -1060,6 +1058,7 @@ const std::string score_src = absl::Substitute( R"( #include <metal_stdlib> +#include "absl/log/absl_check.h" using namespace metal; @@ -1149,8 +1148,7 @@ options:MTLResourceStorageModeShared]; // # filter classes supported is hardware dependent. int max_wg_size = gpu_data_->score_program.maxTotalThreadsPerThreadgroup; - ABSL_CHECK_LT(num_classes_, max_wg_size) - << "# classes must be <" << max_wg_size; + ABSL_CHECK_LT(num_classes_, max_wg_size) << "# classes must be <" << max_wg_size; } #endif // MEDIAPIPE_TFLITE_GL_INFERENCE
diff --git a/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_landmarks_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_landmarks_calculator.cc index 6740f0a..e2c93e8 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_landmarks_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/tflite/tflite_tensors_to_landmarks_calculator.cc
@@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "absl/log/absl_check.h" #include "mediapipe/calculators/tflite/tflite_tensors_to_landmarks_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/formats/landmark.pb.h" #include "mediapipe/framework/port/ret_check.h" #include "tensorflow/lite/interpreter.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/calculators/util/annotation_overlay_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/util/annotation_overlay_calculator.cc index fbd24ee..5afede99 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/util/annotation_overlay_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/util/annotation_overlay_calculator.cc
@@ -14,7 +14,6 @@ #include <memory> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "mediapipe/calculators/util/annotation_overlay_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" @@ -173,7 +172,7 @@ REGISTER_CALCULATOR(AnnotationOverlayCalculator); absl::Status AnnotationOverlayCalculator::GetContract(CalculatorContract* cc) { - ABSL_CHECK_GE(cc->Inputs().NumEntries(), 1); + RET_CHECK_GE(cc->Inputs().NumEntries(), 1); bool use_gpu = false; @@ -190,13 +189,13 @@ #if !MEDIAPIPE_DISABLE_GPU if (cc->Inputs().HasTag(kGpuBufferTag)) { cc->Inputs().Tag(kGpuBufferTag).Set<mediapipe::GpuBuffer>(); - CHECK(cc->Outputs().HasTag(kGpuBufferTag)); + RET_CHECK(cc->Outputs().HasTag(kGpuBufferTag)); use_gpu = true; } #endif // !MEDIAPIPE_DISABLE_GPU if (cc->Inputs().HasTag(kImageFrameTag)) { cc->Inputs().Tag(kImageFrameTag).Set<ImageFrame>(); - CHECK(cc->Outputs().HasTag(kImageFrameTag)); + RET_CHECK(cc->Outputs().HasTag(kImageFrameTag)); } // Data streams to render.
diff --git a/third_party/mediapipe/src/mediapipe/calculators/util/association_calculator.h b/third_party/mediapipe/src/mediapipe/calculators/util/association_calculator.h index 1cec63c..7ea53720 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/util/association_calculator.h +++ b/third_party/mediapipe/src/mediapipe/calculators/util/association_calculator.h
@@ -18,7 +18,6 @@ #include <memory> #include <vector> -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "mediapipe/calculators/util/association_calculator.pb.h" #include "mediapipe/framework/calculator_context.h" @@ -28,6 +27,7 @@ #include "mediapipe/framework/port/rectangle.h" #include "mediapipe/framework/port/status.h" #include "mediapipe/util/rectangle_util.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/calculators/util/detection_label_id_to_text_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/util/detection_label_id_to_text_calculator.cc index 0c1d6892..44b7a210 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/util/detection_label_id_to_text_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/util/detection_label_id_to_text_calculator.cc
@@ -19,6 +19,7 @@ #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/proto_ns.h" #include "mediapipe/framework/port/status.h" +#include "mediapipe/framework/port/status_macros.h" #include "mediapipe/util/label_map.pb.h" #include "mediapipe/util/resource_util.h" @@ -85,7 +86,8 @@ ASSIGN_OR_RETURN(string_path, PathToResourceAsFile(options.label_map_path())); std::string label_map_string; - MP_RETURN_IF_ERROR(file::GetContents(string_path, &label_map_string)); + MP_RETURN_IF_ERROR( + mediapipe::GetResourceContents(string_path, &label_map_string)); std::istringstream stream(label_map_string); std::string line;
diff --git a/third_party/mediapipe/src/mediapipe/calculators/util/detections_to_render_data_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/util/detections_to_render_data_calculator.cc index 7343009..2d53d94c 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/util/detections_to_render_data_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/util/detections_to_render_data_calculator.cc
@@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" @@ -24,6 +23,7 @@ #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/util/color.pb.h" #include "mediapipe/util/render_data.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace { @@ -234,7 +234,7 @@ const Detection& detection, const DetectionsToRenderDataCalculatorOptions& options, float text_line_height, RenderData* render_data) { - CHECK(detection.label().empty() || detection.label_id().empty() || + ABSL_CHECK(detection.label().empty() || detection.label_id().empty() || detection.label_size() == detection.label_id_size()) << "String or integer labels should be of same size. Or only one of them " "is present."; @@ -362,7 +362,7 @@ const Detection& detection, const DetectionsToRenderDataCalculatorOptions& options, RenderData* render_data) { - CHECK(detection.location_data().format() == LocationData::BOUNDING_BOX || + ABSL_CHECK(detection.location_data().format() == LocationData::BOUNDING_BOX || detection.location_data().format() == LocationData::RELATIVE_BOUNDING_BOX) << "Only Detection with formats of BOUNDING_BOX or RELATIVE_BOUNDING_BOX "
diff --git a/third_party/mediapipe/src/mediapipe/calculators/util/labels_to_render_data_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/util/labels_to_render_data_calculator.cc index 314640e..6bdebcef 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/util/labels_to_render_data_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/util/labels_to_render_data_calculator.cc
@@ -19,7 +19,6 @@ #include <string> #include <vector> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "mediapipe/calculators/util/labels_to_render_data_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" @@ -30,6 +29,7 @@ #include "mediapipe/framework/port/statusor.h" #include "mediapipe/util/color.pb.h" #include "mediapipe/util/render_data.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -115,8 +115,7 @@ video_height_ = video_header.height; return absl::OkStatus(); } else { - ABSL_CHECK_EQ(options_.location(), - LabelsToRenderDataCalculatorOptions::TOP_LEFT) + ABSL_CHECK_EQ(options_.location(), LabelsToRenderDataCalculatorOptions::TOP_LEFT) << "Only TOP_LEFT is supported without VIDEO_PRESTREAM."; }
diff --git a/third_party/mediapipe/src/mediapipe/calculators/util/landmarks_refinement_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/util/landmarks_refinement_calculator.cc index 8f734ac..3e653cb 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/util/landmarks_refinement_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/util/landmarks_refinement_calculator.cc
@@ -25,6 +25,7 @@ #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/port/proto_ns.h" #include "mediapipe/framework/port/ret_check.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -102,7 +103,7 @@ ->set_z(z_average); } } else { - CHECK(false) << "Z refinement is either not specified or not supported"; + ABSL_CHECK(false) << "Z refinement is either not specified or not supported"; } }
diff --git a/third_party/mediapipe/src/mediapipe/calculators/util/non_max_suppression_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/util/non_max_suppression_calculator.cc index 1bab7af..828f5a3 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/util/non_max_suppression_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/util/non_max_suppression_calculator.cc
@@ -18,7 +18,6 @@ #include <utility> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/calculators/util/non_max_suppression_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/formats/detection.pb.h" @@ -27,6 +26,7 @@ #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/rectangle.h" #include "mediapipe/framework/port/status.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -48,7 +48,7 @@ if (detection->label_id_size() == 0 && detection->label_size() == 0) { return false; } - CHECK(detection->label_id_size() == detection->score_size() || + ABSL_CHECK(detection->label_id_size() == detection->score_size() || detection->label_size() == detection->score_size()) << "Number of scores must be equal to number of detections.";
diff --git a/third_party/mediapipe/src/mediapipe/calculators/util/rect_to_render_data_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/util/rect_to_render_data_calculator.cc index bbc08255e..c5e779a 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/util/rect_to_render_data_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/util/rect_to_render_data_calculator.cc
@@ -18,6 +18,7 @@ #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/util/color.pb.h" #include "mediapipe/util/render_data.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -41,8 +42,8 @@ annotation->set_thickness(options.thickness()); if (options.has_top_left_thickness()) { - CHECK(!options.oval()); - CHECK(!options.filled()); + ABSL_CHECK(!options.oval()); + ABSL_CHECK(!options.filled()); annotation->mutable_rectangle()->set_top_left_thickness( options.top_left_thickness()); }
diff --git a/third_party/mediapipe/src/mediapipe/calculators/video/box_detector_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/video/box_detector_calculator.cc index 4767d4d..421556b 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/video/box_detector_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/video/box_detector_calculator.cc
@@ -17,7 +17,6 @@ #include <memory> #include <unordered_set> -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/strings/numbers.h" #include "mediapipe/calculators/video/box_detector_calculator.pb.h" @@ -44,6 +43,7 @@ #include "mediapipe/util/android/file/base/helpers.h" #else #include "mediapipe/framework/port/file_helpers.h" +#include "absl/log/absl_check.h" #endif namespace mediapipe { @@ -277,7 +277,7 @@ ? &(cc->Inputs().Tag(kDescriptorsTag)) : nullptr; - CHECK(track_stream != nullptr || video_stream != nullptr || + ABSL_CHECK(track_stream != nullptr || video_stream != nullptr || (feature_stream != nullptr && descriptor_stream != nullptr)) << "One and only one of {tracking_data, input image frame, " "feature/descriptor} need to be valid."; @@ -296,7 +296,7 @@ const TrackingData& tracking_data = track_stream->Get<TrackingData>(); - CHECK(tracked_boxes_stream != nullptr) << "tracked_boxes needed."; + ABSL_CHECK(tracked_boxes_stream != nullptr) << "tracked_boxes needed."; const TimedBoxProtoList tracked_boxes = tracked_boxes_stream->Get<TimedBoxProtoList>();
diff --git a/third_party/mediapipe/src/mediapipe/calculators/video/box_tracker_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/video/box_tracker_calculator.cc index c71e6857..90068e3 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/video/box_tracker_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/video/box_tracker_calculator.cc
@@ -22,7 +22,6 @@ #include "absl/container/flat_hash_set.h" #include "absl/container/node_hash_map.h" #include "absl/container/node_hash_set.h" -#include "absl/log/absl_check.h" #include "absl/strings/numbers.h" #include "mediapipe/calculators/video/box_tracker_calculator.pb.h" #include "mediapipe/framework/calculator_framework.h" @@ -38,6 +37,7 @@ #include "mediapipe/util/tracking/box_tracker.h" #include "mediapipe/util/tracking/tracking.h" #include "mediapipe/util/tracking/tracking_visualization_utilities.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -316,10 +316,10 @@ float in_right, int rotation, float* out_top, float* out_left, float* out_bottom, float* out_right) { - CHECK(out_top != nullptr); - CHECK(out_left != nullptr); - CHECK(out_bottom != nullptr); - CHECK(out_right != nullptr); + ABSL_CHECK(out_top != nullptr); + ABSL_CHECK(out_left != nullptr); + ABSL_CHECK(out_bottom != nullptr); + ABSL_CHECK(out_right != nullptr); const float in_center_x = (in_left + in_right) * 0.5f; const float in_center_y = (in_top + in_bottom) * 0.5f; const float in_width = in_right - in_left; @@ -374,7 +374,7 @@ void AddStateToPath(const MotionBoxState& state, int64_t time_msec, PathSegment* path) { - CHECK(path); + ABSL_CHECK(path); TimedBox result; TimedBoxFromMotionBoxState(state, &result); result.time_msec = time_msec; @@ -650,7 +650,7 @@ // present at this frame. TimedBoxProtoList box_track_list; - CHECK(box_tracker_ || track_stream) + ABSL_CHECK(box_tracker_ || track_stream) << "Expected either batch or streaming mode"; // Corresponding list of box states for rendering. For each id present at @@ -1167,8 +1167,8 @@ int64_t duration_ms, bool forward, MotionBoxMap* box_map, std::vector<int>* failed_ids) { - CHECK(box_map); - CHECK(failed_ids); + ABSL_CHECK(box_map); + ABSL_CHECK(failed_ids); // Cache the actively discarded tracked ids from the new tracking data. for (const int discarded_id :
diff --git a/third_party/mediapipe/src/mediapipe/calculators/video/flow_packager_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/video/flow_packager_calculator.cc index f30fed88b..f5f8dd0648 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/video/flow_packager_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/video/flow_packager_calculator.cc
@@ -17,7 +17,6 @@ #include <fstream> #include <memory> -#include "absl/log/absl_check.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "mediapipe/calculators/video/flow_packager_calculator.pb.h" @@ -27,6 +26,7 @@ #include "mediapipe/util/tracking/camera_motion.pb.h" #include "mediapipe/util/tracking/flow_packager.h" #include "mediapipe/util/tracking/region_flow.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -267,7 +267,7 @@ void FlowPackagerCalculator::PrepareCurrentForNextChunk( TrackingDataChunk* chunk) { - CHECK(chunk); + ABSL_CHECK(chunk); if (chunk->item_size() == 0) { LOG(ERROR) << "Called with empty chunk. Unexpected."; return;
diff --git a/third_party/mediapipe/src/mediapipe/calculators/video/motion_analysis_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/video/motion_analysis_calculator.cc index fe320b1..9a33df4 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/video/motion_analysis_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/video/motion_analysis_calculator.cc
@@ -17,7 +17,6 @@ #include <memory> #include <string> -#include "absl/log/absl_check.h" #include "absl/strings/numbers.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" @@ -37,6 +36,7 @@ #include "mediapipe/util/tracking/motion_estimation.h" #include "mediapipe/util/tracking/motion_models.h" #include "mediapipe/util/tracking/region_flow.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -429,7 +429,7 @@ selection_input_ ? &(cc->Inputs().Tag(kSelectionTag)) : nullptr; // Checked on Open. - CHECK(video_stream || selection_stream); + ABSL_CHECK(video_stream || selection_stream); // Lazy init. if (frame_width_ < 0 || frame_height_ < 0) { @@ -473,7 +473,7 @@ // Always use frame if selection is not activated. bool use_frame = !selection_input_; if (selection_input_) { - CHECK(selection_stream); + ABSL_CHECK(selection_stream); // Fill in timestamps we process. if (!selection_stream->Value().IsEmpty()) { @@ -767,7 +767,7 @@ // Filled by CSV file parsing. if (!meta_homographies_.empty()) { - CHECK(csv_file_input_); + ABSL_CHECK(csv_file_input_); AppendCameraMotionsFromHomographies(meta_homographies_, true, // append identity. &meta_motions_, &meta_features_); @@ -814,7 +814,7 @@ bool MotionAnalysisCalculator::HomographiesFromValues( const std::vector<float>& homog_values, std::deque<Homography>* homographies) { - CHECK(homographies); + ABSL_CHECK(homographies); // Obvious constants are obvious :D constexpr int kHomographyValues = 9; @@ -856,7 +856,7 @@ void MotionAnalysisCalculator::SubtractMetaMotion( const CameraMotion& meta_motion, RegionFlowFeatureList* features) { if (meta_motion.mixture_homography().model_size() > 0) { - CHECK(row_weights_ != nullptr); + ABSL_CHECK(row_weights_ != nullptr); RegionFlowFeatureListViaTransform(meta_motion.mixture_homography(), features, -1.0f, 1.0f, // subtract transformed. @@ -923,8 +923,8 @@ const std::deque<Homography>& homographies, bool append_identity, std::deque<CameraMotion>* camera_motions, std::deque<RegionFlowFeatureList>* features) { - CHECK(camera_motions); - CHECK(features); + ABSL_CHECK(camera_motions); + ABSL_CHECK(features); CameraMotion identity; identity.set_frame_width(frame_width_); @@ -948,8 +948,7 @@ } const int models_per_frame = options_.meta_models_per_frame(); - ABSL_CHECK_GT(models_per_frame, 0) - << "At least one model per frame is needed"; + ABSL_CHECK_GT(models_per_frame, 0) << "At least one model per frame is needed"; ABSL_CHECK_EQ(0, homographies.size() % models_per_frame); const int num_frames = homographies.size() / models_per_frame;
diff --git a/third_party/mediapipe/src/mediapipe/calculators/video/tool/flow_quantizer_model.cc b/third_party/mediapipe/src/mediapipe/calculators/video/tool/flow_quantizer_model.cc index 146dc4a..81983819 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/video/tool/flow_quantizer_model.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/video/tool/flow_quantizer_model.cc
@@ -14,9 +14,9 @@ #include "mediapipe/calculators/video/tool/flow_quantizer_model.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/type_map.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/calculators/video/tvl1_optical_flow_calculator.cc b/third_party/mediapipe/src/mediapipe/calculators/video/tvl1_optical_flow_calculator.cc index dd87ae6..5ce6eab 100644 --- a/third_party/mediapipe/src/mediapipe/calculators/video/tvl1_optical_flow_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/calculators/video/tvl1_optical_flow_calculator.cc
@@ -13,13 +13,13 @@ // limitations under the License. #include "absl/base/macros.h" -#include "absl/log/absl_check.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/formats/image_frame.h" #include "mediapipe/framework/formats/image_frame_opencv.h" #include "mediapipe/framework/formats/motion/optical_flow_field.h" #include "mediapipe/framework/port/opencv_video_inc.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace { @@ -159,7 +159,7 @@ absl::Status Tvl1OpticalFlowCalculator::CalculateOpticalFlow( const ImageFrame& current_frame, const ImageFrame& next_frame, OpticalFlowField* flow) { - CHECK(flow); + ABSL_CHECK(flow); if (!ImageSizesMatch(current_frame, next_frame)) { return tool::StatusInvalid("Images are different sizes."); }
diff --git a/third_party/mediapipe/src/mediapipe/framework/BUILD b/third_party/mediapipe/src/mediapipe/framework/BUILD index 93e9475f..8a22d23 100644 --- a/third_party/mediapipe/src/mediapipe/framework/BUILD +++ b/third_party/mediapipe/src/mediapipe/framework/BUILD
@@ -44,6 +44,9 @@ "encode_binary_proto.bzl", ], visibility = ["//visibility:public"], + deps = [ + "@bazel_skylib//lib:paths", + ], ) alias( @@ -179,8 +182,8 @@ ":timestamp", "//mediapipe/framework/deps:registration", "//mediapipe/framework/port:logging", - "//mediapipe/framework/port:status", "@com_google_absl//absl/memory", + "@com_google_absl//absl/status", ], ) @@ -269,6 +272,8 @@ ], deps = [ ":calculator_base", + ":calculator_context", + ":calculator_contract", ":calculator_graph", ":calculator_registry", ":counter_factory", @@ -355,6 +360,7 @@ "@com_google_absl//absl/container:fixed_array", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log", "@com_google_absl//absl/memory", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", @@ -1649,6 +1655,7 @@ ":packet", ":packet_test_cc_proto", ":type_map", + "//mediapipe/framework/api2:builder", "//mediapipe/framework/port:core_proto", "//mediapipe/framework/port:gtest_main", "@com_google_absl//absl/strings",
diff --git a/third_party/mediapipe/src/mediapipe/framework/api2/builder.h b/third_party/mediapipe/src/mediapipe/framework/api2/builder.h index 89f25c7..ee478e0 100644 --- a/third_party/mediapipe/src/mediapipe/framework/api2/builder.h +++ b/third_party/mediapipe/src/mediapipe/framework/api2/builder.h
@@ -11,7 +11,6 @@ #include <vector> #include "absl/container/btree_map.h" -#include "absl/log/absl_check.h" #include "absl/strings/string_view.h" #include "google/protobuf/message_lite.h" #include "mediapipe/framework/api2/port.h" @@ -19,6 +18,7 @@ #include "mediapipe/framework/calculator_contract.h" #include "mediapipe/framework/port/any_proto.h" #include "mediapipe/framework/port/ret_check.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace api2 { @@ -194,7 +194,7 @@ template <typename U, typename std::enable_if<AllowConnection<U>{}, int>::type = 0> Src& ConnectTo(const Dst<U>& dest) { - CHECK(dest.base_.source == nullptr); + ABSL_CHECK(dest.base_.source == nullptr); dest.base_.source = base_; base_->dests_.emplace_back(&dest.base_); return *this; @@ -783,7 +783,7 @@ config->set_calculator(node.type_); node.in_streams_.Visit( [&](const TagIndexLocation& loc, const DestinationBase& endpoint) { - CHECK(endpoint.source != nullptr); + ABSL_CHECK(endpoint.source != nullptr); config->add_input_stream(TaggedName(loc, endpoint.source->name_)); }); node.out_streams_.Visit( @@ -792,7 +792,7 @@ }); node.in_sides_.Visit([&](const TagIndexLocation& loc, const DestinationBase& endpoint) { - CHECK(endpoint.source != nullptr); + ABSL_CHECK(endpoint.source != nullptr); config->add_input_side_packet(TaggedName(loc, endpoint.source->name_)); }); node.out_sides_.Visit( @@ -813,7 +813,7 @@ config->set_packet_generator(node.type_); node.in_sides_.Visit([&](const TagIndexLocation& loc, const DestinationBase& endpoint) { - CHECK(endpoint.source != nullptr); + ABSL_CHECK(endpoint.source != nullptr); config->add_input_side_packet(TaggedName(loc, endpoint.source->name_)); }); node.out_sides_.Visit( @@ -830,7 +830,7 @@ absl::Status UpdateBoundaryConfig(CalculatorGraphConfig* config) { graph_boundary_.in_streams_.Visit( [&](const TagIndexLocation& loc, const DestinationBase& endpoint) { - CHECK(endpoint.source != nullptr); + ABSL_CHECK(endpoint.source != nullptr); config->add_output_stream(TaggedName(loc, endpoint.source->name_)); }); graph_boundary_.out_streams_.Visit( @@ -839,7 +839,7 @@ }); graph_boundary_.in_sides_.Visit([&](const TagIndexLocation& loc, const DestinationBase& endpoint) { - CHECK(endpoint.source != nullptr); + ABSL_CHECK(endpoint.source != nullptr); config->add_output_side_packet(TaggedName(loc, endpoint.source->name_)); }); graph_boundary_.out_sides_.Visit(
diff --git a/third_party/mediapipe/src/mediapipe/framework/api2/packet.h b/third_party/mediapipe/src/mediapipe/framework/api2/packet.h index f231f4c..d1da71c 100644 --- a/third_party/mediapipe/src/mediapipe/framework/api2/packet.h +++ b/third_party/mediapipe/src/mediapipe/framework/api2/packet.h
@@ -13,11 +13,11 @@ #include <functional> #include <type_traits> -#include "absl/log/absl_check.h" #include "absl/meta/type_traits.h" #include "mediapipe/framework/api2/tuple.h" #include "mediapipe/framework/packet.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace api2 {
diff --git a/third_party/mediapipe/src/mediapipe/framework/api2/port.h b/third_party/mediapipe/src/mediapipe/framework/api2/port.h index 075e884..cf6f40f 100644 --- a/third_party/mediapipe/src/mediapipe/framework/api2/port.h +++ b/third_party/mediapipe/src/mediapipe/framework/api2/port.h
@@ -20,7 +20,6 @@ #include <type_traits> #include <utility> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "mediapipe/framework/api2/const_str.h" @@ -30,6 +29,7 @@ #include "mediapipe/framework/output_side_packet.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/tool/type_util.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace api2 {
diff --git a/third_party/mediapipe/src/mediapipe/framework/calculator_base.h b/third_party/mediapipe/src/mediapipe/framework/calculator_base.h index 19f37f9de..1f4c821 100644 --- a/third_party/mediapipe/src/mediapipe/framework/calculator_base.h +++ b/third_party/mediapipe/src/mediapipe/framework/calculator_base.h
@@ -17,14 +17,16 @@ #ifndef MEDIAPIPE_FRAMEWORK_CALCULATOR_BASE_H_ #define MEDIAPIPE_FRAMEWORK_CALCULATOR_BASE_H_ +#include <memory> +#include <string> #include <type_traits> #include "absl/memory/memory.h" +#include "absl/status/status.h" #include "mediapipe/framework/calculator_context.h" #include "mediapipe/framework/calculator_contract.h" #include "mediapipe/framework/deps/registration.h" #include "mediapipe/framework/port.h" -#include "mediapipe/framework/port/status.h" #include "mediapipe/framework/timestamp.h" namespace mediapipe { @@ -150,8 +152,9 @@ // Packets may be output during a call to Close(). However, output packets // are silently discarded if Close() is called after a graph run has ended. // - // NOTE: If Close() needs to perform an action only when processing is - // complete, Close() must check if cc->GraphStatus() is OK. + // NOTE: Do not call cc->GraphStatus() in Close() if you need to check if the + // processing is complete. Please, see CalculatorContext::GraphStatus + // documentation for the suggested solution. virtual absl::Status Close(CalculatorContext* cc) { return absl::OkStatus(); } // Returns a value according to which the framework selects
diff --git a/third_party/mediapipe/src/mediapipe/framework/calculator_context.cc b/third_party/mediapipe/src/mediapipe/framework/calculator_context.cc index 4452f45e..dcedac9 100644 --- a/third_party/mediapipe/src/mediapipe/framework/calculator_context.cc +++ b/third_party/mediapipe/src/mediapipe/framework/calculator_context.cc
@@ -13,36 +13,37 @@ // limitations under the License. #include "mediapipe/framework/calculator_context.h" +#include "absl/log/absl_check.h" namespace mediapipe { const std::string& CalculatorContext::CalculatorType() const { - CHECK(calculator_state_); + ABSL_CHECK(calculator_state_); return calculator_state_->CalculatorType(); } const CalculatorOptions& CalculatorContext::Options() const { - CHECK(calculator_state_); + ABSL_CHECK(calculator_state_); return calculator_state_->Options(); } const std::string& CalculatorContext::NodeName() const { - CHECK(calculator_state_); + ABSL_CHECK(calculator_state_); return calculator_state_->NodeName(); } int CalculatorContext::NodeId() const { - CHECK(calculator_state_); + ABSL_CHECK(calculator_state_); return calculator_state_->NodeId(); } Counter* CalculatorContext::GetCounter(const std::string& name) { - CHECK(calculator_state_); + ABSL_CHECK(calculator_state_); return calculator_state_->GetCounter(name); } CounterFactory* CalculatorContext::GetCounterFactory() { - CHECK(calculator_state_); + ABSL_CHECK(calculator_state_); return calculator_state_->GetCounterFactory(); }
diff --git a/third_party/mediapipe/src/mediapipe/framework/calculator_context.h b/third_party/mediapipe/src/mediapipe/framework/calculator_context.h index 315d265..fe1a8d9 100644 --- a/third_party/mediapipe/src/mediapipe/framework/calculator_context.h +++ b/third_party/mediapipe/src/mediapipe/framework/calculator_context.h
@@ -20,7 +20,6 @@ #include <string> #include <utility> -#include "absl/log/absl_check.h" #include "mediapipe/framework/calculator_state.h" #include "mediapipe/framework/counter.h" #include "mediapipe/framework/graph_service.h" @@ -31,6 +30,7 @@ #include "mediapipe/framework/port/any_proto.h" #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/timestamp.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/framework/calculator_context_manager.cc b/third_party/mediapipe/src/mediapipe/framework/calculator_context_manager.cc index acd70dd9..ea62463 100644 --- a/third_party/mediapipe/src/mediapipe/framework/calculator_context_manager.cc +++ b/third_party/mediapipe/src/mediapipe/framework/calculator_context_manager.cc
@@ -19,6 +19,7 @@ #include "absl/memory/memory.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -27,7 +28,7 @@ std::shared_ptr<tool::TagMap> input_tag_map, std::shared_ptr<tool::TagMap> output_tag_map, bool calculator_run_in_parallel) { - CHECK(calculator_state); + ABSL_CHECK(calculator_state); calculator_state_ = calculator_state; input_tag_map_ = std::move(input_tag_map); output_tag_map_ = std::move(output_tag_map); @@ -51,15 +52,15 @@ CalculatorContext* CalculatorContextManager::GetDefaultCalculatorContext() const { - CHECK(default_context_.get()); + ABSL_CHECK(default_context_.get()); return default_context_.get(); } CalculatorContext* CalculatorContextManager::GetFrontCalculatorContext( Timestamp* context_input_timestamp) { - CHECK(calculator_run_in_parallel_); + ABSL_CHECK(calculator_run_in_parallel_); absl::MutexLock lock(&contexts_mutex_); - CHECK(!active_contexts_.empty()); + ABSL_CHECK(!active_contexts_.empty()); *context_input_timestamp = active_contexts_.begin()->first; return active_contexts_.begin()->second.get(); } @@ -70,7 +71,7 @@ return GetDefaultCalculatorContext(); } absl::MutexLock lock(&contexts_mutex_); - CHECK(!mediapipe::ContainsKey(active_contexts_, input_timestamp)) + ABSL_CHECK(!mediapipe::ContainsKey(active_contexts_, input_timestamp)) << "Multiple invocations with the same timestamps are not allowed with " "parallel execution, input_timestamp = " << input_timestamp;
diff --git a/third_party/mediapipe/src/mediapipe/framework/calculator_context_manager.h b/third_party/mediapipe/src/mediapipe/framework/calculator_context_manager.h index ae697e1..e327787 100644 --- a/third_party/mediapipe/src/mediapipe/framework/calculator_context_manager.h +++ b/third_party/mediapipe/src/mediapipe/framework/calculator_context_manager.h
@@ -21,7 +21,6 @@ #include <memory> #include "absl/base/thread_annotations.h" -#include "absl/log/absl_check.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/calculator_context.h" #include "mediapipe/framework/calculator_state.h" @@ -29,6 +28,7 @@ #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/timestamp.h" #include "mediapipe/framework/tool/tag_map.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/framework/calculator_framework.h b/third_party/mediapipe/src/mediapipe/framework/calculator_framework.h index afb73fb3..8f193fd 100644 --- a/third_party/mediapipe/src/mediapipe/framework/calculator_framework.h +++ b/third_party/mediapipe/src/mediapipe/framework/calculator_framework.h
@@ -52,6 +52,8 @@ #define MEDIAPIPE_FRAMEWORK_CALCULATOR_FRAMEWORK_H_ #include "mediapipe/framework/calculator_base.h" +#include "mediapipe/framework/calculator_context.h" +#include "mediapipe/framework/calculator_contract.h" #include "mediapipe/framework/calculator_graph.h" #include "mediapipe/framework/calculator_registry.h" #include "mediapipe/framework/counter_factory.h"
diff --git a/third_party/mediapipe/src/mediapipe/framework/calculator_graph.cc b/third_party/mediapipe/src/mediapipe/framework/calculator_graph.cc index 770f8337..a806ea5 100644 --- a/third_party/mediapipe/src/mediapipe/framework/calculator_graph.cc +++ b/third_party/mediapipe/src/mediapipe/framework/calculator_graph.cc
@@ -25,7 +25,7 @@ #include "absl/container/fixed_array.h" #include "absl/container/flat_hash_set.h" -#include "absl/log/absl_check.h" +#include "absl/log/log.h" #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" @@ -66,6 +66,7 @@ #include "mediapipe/gpu/gpu_service.h" #include "mediapipe/gpu/graph_support.h" #include "mediapipe/util/cpu_util.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -76,6 +77,11 @@ constexpr int kMaxNumAccumulatedErrors = 1000; constexpr char kApplicationThreadExecutorType[] = "ApplicationThreadExecutor"; +// Do not log status payloads, but do include stack traces. +constexpr absl::StatusToStringMode kStatusLogFlags = + absl::StatusToStringMode::kWithEverything & + (~absl::StatusToStringMode::kWithPayload); + } // namespace void CalculatorGraph::ScheduleAllOpenableNodes() { @@ -156,7 +162,7 @@ Executor* default_executor = nullptr; if (!use_application_thread_) { default_executor = executors_[""].get(); - CHECK(default_executor); + ABSL_CHECK(default_executor); } // If default_executor is nullptr, then packet_generator_graph_ will create // its own DelegatingExecutor to use the application thread. @@ -383,6 +389,7 @@ "", std::make_shared<internal::DelegatingExecutor>( std::bind(&internal::Scheduler::AddApplicationThreadTask, &scheduler_, std::placeholders::_1)))); + VLOG(1) << "Using default executor and application thread."; return absl::OkStatus(); } @@ -402,6 +409,8 @@ } MP_RETURN_IF_ERROR( CreateDefaultThreadPool(default_executor_options, num_threads)); + VLOG(1) << absl::StrCat("Using default executor with num_threads: ", + num_threads); return absl::OkStatus(); } @@ -708,7 +717,7 @@ absl::Status error_status; if (has_error_) { GetCombinedErrors(&error_status); - LOG(ERROR) << error_status; + LOG(ERROR) << error_status.ToString(kStatusLogFlags); return error_status; } @@ -787,7 +796,7 @@ } if (GetCombinedErrors(&error_status)) { - LOG(ERROR) << error_status; + LOG(ERROR) << error_status.ToString(kStatusLogFlags); CleanupAfterRun(&error_status); return error_status; } @@ -851,7 +860,7 @@ VLOG(2) << "Scheduler idle."; absl::Status status = absl::OkStatus(); if (GetCombinedErrors(&status)) { - LOG(ERROR) << status; + LOG(ERROR) << status.ToString(kStatusLogFlags); } return status; } @@ -1053,8 +1062,7 @@ } bool CalculatorGraph::GetCombinedErrors(absl::Status* error_status) { - return GetCombinedErrors("CalculatorGraph::Run() failed in Run: ", - error_status); + return GetCombinedErrors("CalculatorGraph::Run() failed: ", error_status); } bool CalculatorGraph::GetCombinedErrors(const std::string& error_prefix, @@ -1093,7 +1101,7 @@ absl::StatusOr<std::unique_ptr<internal::StaticAccessToStatusHandler>> static_access_statusor = internal::StaticAccessToStatusHandlerRegistry:: CreateByNameInNamespace(validated_graph_->Package(), handler_type); - CHECK(static_access_statusor.ok()) << handler_type << " is not registered."; + ABSL_CHECK(static_access_statusor.ok()) << handler_type << " is not registered."; auto static_access = std::move(static_access_statusor).value(); absl::Status handler_result; if (graph_run_state == GraphRunState::PRE_RUN) { @@ -1134,7 +1142,7 @@ upstream_nodes = &validated_graph_->CalculatorInfos()[node_index].AncestorSources(); } - CHECK(upstream_nodes); + ABSL_CHECK(upstream_nodes); std::vector<CalculatorNode*> nodes_to_schedule; { @@ -1341,7 +1349,7 @@ // Obtain the combined status again, so that it includes the new errors // added by CallStatusHandlers. GetCombinedErrors(status); - CHECK(!status->ok()); + ABSL_CHECK(!status->ok()); } else { MEDIAPIPE_CHECK_OK(*status); }
diff --git a/third_party/mediapipe/src/mediapipe/framework/calculator_node.cc b/third_party/mediapipe/src/mediapipe/framework/calculator_node.cc index 5618038..57d60eb3 100644 --- a/third_party/mediapipe/src/mediapipe/framework/calculator_node.cc +++ b/third_party/mediapipe/src/mediapipe/framework/calculator_node.cc
@@ -19,7 +19,6 @@ #include <unordered_map> #include <utility> -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" @@ -47,6 +46,7 @@ #include "mediapipe/framework/tool/status_util.h" #include "mediapipe/framework/tool/tag_map.h" #include "mediapipe/framework/tool/validate_name.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -60,7 +60,7 @@ } else { id = packet_type_set.GetId(tag, 0); } - CHECK(id.IsValid()) << "Internal mediapipe error."; + ABSL_CHECK(id.IsValid()) << "Internal mediapipe error."; return &packet_type_set.Get(id); } @@ -367,7 +367,7 @@ } void CalculatorNode::SetMaxInputStreamQueueSize(int max_queue_size) { - CHECK(input_stream_handler_); + ABSL_CHECK(input_stream_handler_); input_stream_handler_->SetMaxQueueSize(max_queue_size); } @@ -696,7 +696,7 @@ { absl::MutexLock lock(&status_mutex_); ABSL_CHECK_EQ(status_, kStatePrepared) << DebugName(); - CHECK(!input_stream_headers_ready_called_); + ABSL_CHECK(!input_stream_headers_ready_called_); input_stream_headers_ready_called_ = true; input_stream_headers_ready_ = true; ready_for_open = input_side_packets_ready_; @@ -711,7 +711,7 @@ { absl::MutexLock lock(&status_mutex_); ABSL_CHECK_EQ(status_, kStatePrepared) << DebugName(); - CHECK(!input_side_packets_ready_called_); + ABSL_CHECK(!input_side_packets_ready_called_); input_side_packets_ready_called_ = true; input_side_packets_ready_ = true; ready_for_open = input_stream_headers_ready_; @@ -791,7 +791,7 @@ } std::string CalculatorNode::DebugName() const { - DCHECK(calculator_state_); + ABSL_DCHECK(calculator_state_); return calculator_state_->NodeName(); } @@ -895,8 +895,8 @@ // too. // If the streams are closed, there shouldn't be more input. ABSL_CHECK_EQ(calculator_context_manager_.NumberOfContextTimestamps( - *calculator_context), - 1); + *calculator_context), + 1); return CloseNode(absl::OkStatus(), /*graph_run_ended=*/false); } else { RET_CHECK_FAIL() @@ -911,7 +911,7 @@ void CalculatorNode::SetQueueSizeCallbacks( InputStreamManager::QueueSizeCallback becomes_full_callback, InputStreamManager::QueueSizeCallback becomes_not_full_callback) { - CHECK(input_stream_handler_); + ABSL_CHECK(input_stream_handler_); input_stream_handler_->SetQueueSizeCallbacks( std::move(becomes_full_callback), std::move(becomes_not_full_callback)); }
diff --git a/third_party/mediapipe/src/mediapipe/framework/calculator_runner.cc b/third_party/mediapipe/src/mediapipe/framework/calculator_runner.cc index 1bd3211..864552f 100644 --- a/third_party/mediapipe/src/mediapipe/framework/calculator_runner.cc +++ b/third_party/mediapipe/src/mediapipe/framework/calculator_runner.cc
@@ -22,6 +22,7 @@ #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/status.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -139,7 +140,7 @@ #if !defined(MEDIAPIPE_PROTO_LITE) CalculatorRunner::CalculatorRunner(const std::string& node_config_string) { CalculatorGraphConfig::Node node_config; - CHECK( + ABSL_CHECK( proto_ns::TextFormat::ParseFromString(node_config_string, &node_config)); MEDIAPIPE_CHECK_OK(InitializeFromNodeConfig(node_config)); } @@ -149,7 +150,7 @@ int num_inputs, int num_outputs, int num_side_packets) { node_config_.set_calculator(calculator_type); - CHECK(proto_ns::TextFormat::ParseFromString(options_string, + ABSL_CHECK(proto_ns::TextFormat::ParseFromString(options_string, node_config_.mutable_options())); SetNumInputs(num_inputs); SetNumOutputs(num_outputs); @@ -188,7 +189,7 @@ } void CalculatorRunner::InitializeInputs(const tool::TagAndNameInfo& info) { - CHECK(graph_ == nullptr); + ABSL_CHECK(graph_ == nullptr); MEDIAPIPE_CHECK_OK( tool::SetFromTagAndNameInfo(info, node_config_.mutable_input_stream())); inputs_.reset(new StreamContentsSet(info)); @@ -196,7 +197,7 @@ } void CalculatorRunner::InitializeOutputs(const tool::TagAndNameInfo& info) { - CHECK(graph_ == nullptr); + ABSL_CHECK(graph_ == nullptr); MEDIAPIPE_CHECK_OK( tool::SetFromTagAndNameInfo(info, node_config_.mutable_output_stream())); outputs_.reset(new StreamContentsSet(info)); @@ -205,7 +206,7 @@ void CalculatorRunner::InitializeInputSidePackets( const tool::TagAndNameInfo& info) { - CHECK(graph_ == nullptr); + ABSL_CHECK(graph_ == nullptr); MEDIAPIPE_CHECK_OK(tool::SetFromTagAndNameInfo( info, node_config_.mutable_input_side_packet())); input_side_packets_.reset(new PacketSet(info));
diff --git a/third_party/mediapipe/src/mediapipe/framework/calculator_state.cc b/third_party/mediapipe/src/mediapipe/framework/calculator_state.cc index 3b0264e9..79645e3 100644 --- a/third_party/mediapipe/src/mediapipe/framework/calculator_state.cc +++ b/third_party/mediapipe/src/mediapipe/framework/calculator_state.cc
@@ -20,6 +20,7 @@ #include "absl/strings/str_cat.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -46,23 +47,23 @@ } void CalculatorState::SetInputSidePackets(const PacketSet* input_side_packets) { - CHECK(input_side_packets); + ABSL_CHECK(input_side_packets); input_side_packets_ = input_side_packets; } void CalculatorState::SetOutputSidePackets( OutputSidePacketSet* output_side_packets) { - CHECK(output_side_packets); + ABSL_CHECK(output_side_packets); output_side_packets_ = output_side_packets; } Counter* CalculatorState::GetCounter(const std::string& name) { - CHECK(counter_factory_); + ABSL_CHECK(counter_factory_); return counter_factory_->GetCounter(absl::StrCat(NodeName(), "-", name)); } CounterFactory* CalculatorState::GetCounterFactory() { - CHECK(counter_factory_); + ABSL_CHECK(counter_factory_); return counter_factory_; }
diff --git a/third_party/mediapipe/src/mediapipe/framework/collection.h b/third_party/mediapipe/src/mediapipe/framework/collection.h index a2ab39f..d030db5a 100644 --- a/third_party/mediapipe/src/mediapipe/framework/collection.h +++ b/third_party/mediapipe/src/mediapipe/framework/collection.h
@@ -24,7 +24,6 @@ #include <vector> #include "absl/base/macros.h" -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" @@ -34,6 +33,7 @@ #include "mediapipe/framework/tool/tag_map_helper.h" #include "mediapipe/framework/tool/validate_name.h" #include "mediapipe/framework/type_map.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace internal {
diff --git a/third_party/mediapipe/src/mediapipe/framework/deps/map_util.h b/third_party/mediapipe/src/mediapipe/framework/deps/map_util.h index 05d47b7e..4e14478 100644 --- a/third_party/mediapipe/src/mediapipe/framework/deps/map_util.h +++ b/third_party/mediapipe/src/mediapipe/framework/deps/map_util.h
@@ -28,6 +28,7 @@ #include <utility> #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -53,7 +54,7 @@ const typename M::value_type::second_type& FindOrDie( const M& m, const typename M::value_type::first_type& key) { auto it = m.find(key); - CHECK(it != m.end()) << "Map key not found: " << key; + ABSL_CHECK(it != m.end()) << "Map key not found: " << key; return it->second; } @@ -63,7 +64,7 @@ M& m, // NOLINT const typename M::value_type::first_type& key) { auto it = m.find(key); - CHECK(it != m.end()) << "Map key not found: " << key; + ABSL_CHECK(it != m.end()) << "Map key not found: " << key; return it->second; } @@ -138,7 +139,7 @@ // inserted. template <typename M, typename ReverseM> bool ReverseMap(const M& m, ReverseM* reverse) { - CHECK(reverse != nullptr); + ABSL_CHECK(reverse != nullptr); for (const auto& kv : m) { if (!InsertIfNotPresent(reverse, kv.second, kv.first)) { return false;
diff --git a/third_party/mediapipe/src/mediapipe/framework/deps/mathutil.h b/third_party/mediapipe/src/mediapipe/framework/deps/mathutil.h index 24543d4..d17efc6 100644 --- a/third_party/mediapipe/src/mediapipe/framework/deps/mathutil.h +++ b/third_party/mediapipe/src/mediapipe/framework/deps/mathutil.h
@@ -23,9 +23,9 @@ #include <limits> #include <type_traits> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -355,7 +355,7 @@ template <typename T> // T models LessThanComparable. static const T& Clamp(const T& low, const T& high, const T& value) { // Prevents errors in ordering the arguments. - DCHECK(!(high < low)); + ABSL_DCHECK(!(high < low)); if (high < value) return high; if (value < low) return low; return value;
diff --git a/third_party/mediapipe/src/mediapipe/framework/deps/monotonic_clock.cc b/third_party/mediapipe/src/mediapipe/framework/deps/monotonic_clock.cc index e3cc9de..014a3c8f 100644 --- a/third_party/mediapipe/src/mediapipe/framework/deps/monotonic_clock.cc +++ b/third_party/mediapipe/src/mediapipe/framework/deps/monotonic_clock.cc
@@ -16,10 +16,10 @@ #include "absl/base/macros.h" #include "absl/base/thread_annotations.h" -#include "absl/log/absl_check.h" #include "absl/synchronization/mutex.h" #include "absl/time/time.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -61,7 +61,7 @@ // Absolve this object of responsibility for state_. void ReleaseState() { - CHECK(state_owned_); + ABSL_CHECK(state_owned_); state_owned_ = false; }
diff --git a/third_party/mediapipe/src/mediapipe/framework/deps/registration.h b/third_party/mediapipe/src/mediapipe/framework/deps/registration.h index 9abc30ee..1218e4d8 100644 --- a/third_party/mediapipe/src/mediapipe/framework/deps/registration.h +++ b/third_party/mediapipe/src/mediapipe/framework/deps/registration.h
@@ -28,7 +28,6 @@ #include "absl/base/thread_annotations.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" -#include "absl/log/absl_check.h" #include "absl/meta/type_traits.h" #include "absl/strings/str_join.h" #include "absl/strings/str_split.h" @@ -38,6 +37,7 @@ #include "mediapipe/framework/port/canonical_errors.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/statusor.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -409,22 +409,114 @@ #define REGISTRY_STATIC_VAR(var_name, line) \ REGISTRY_STATIC_VAR_INNER(var_name, line) -#define MEDIAPIPE_REGISTER_FACTORY_FUNCTION(RegistryType, name, ...) \ - static auto* REGISTRY_STATIC_VAR(registration_##name, __LINE__) = \ - new mediapipe::RegistrationToken( \ - RegistryType::Register(#name, __VA_ARGS__)) +// Disables all static registration in MediaPipe accomplished using: +// - REGISTER_FACTORY_FUNCTION_QUALIFIED +// - MEDIAPIPE_REGISTER_FACTORY_FUNCTION +// - MEDIAPIPE_STATIC_REGISTRATOR_TEMPLATE +// +// Which includes: +// - calculators +// - input stream handlers +// - output stream handlers +// - generators +// - anything else registered using above macros +#if !defined(MEDIAPIPE_DISABLE_STATIC_REGISTRATION) +#define MEDIAPIPE_DISABLE_STATIC_REGISTRATION 0 +#endif // !defined(MEDIAPIPE_DISABLE_STATIC_REGISTRATION) + +// Enables "Dry Run" for MediaPipe static registration: MediaPipe logs the +// registration code, instead of actual registration. +// +// The intended use: if you plan to disable static registration using +// MEDIAPIPE_DISABLE_STATIC_REGISTRATION, you may find it useful to build your +// MediaPipe dependency first with only: +// MEDIAPIPE_ENABLE_STATIC_REGISTRATION_DRY_RUN +// and load it to see what manual registration will be required when you build +// with: +// MEDIAPIPE_DISABLE_STATIC_REGISTRATION +#if !defined(MEDIAPIPE_ENABLE_STATIC_REGISTRATION_DRY_RUN) +#define MEDIAPIPE_ENABLE_STATIC_REGISTRATION_DRY_RUN 0 +#endif // !defined(MEDIAPIPE_ENABLE_STATIC_REGISTRATION_DRY_RUN) + +#if MEDIAPIPE_DISABLE_STATIC_REGISTRATION && \ + MEDIAPIPE_ENABLE_STATIC_REGISTRATION_DRY_RUN +static_assert(false, + "Cannot do static registration Dry Run as static registration is " + "disabled."); +#endif // MEDIAPIPE_DISABLE_STATIC_REGISTRATION && + // MEDIAPIPE_ENABLE_STATIC_REGISTRATION_DRY_RUN + +#if MEDIAPIPE_DISABLE_STATIC_REGISTRATION +// When static registration is disabled, make sure corresponding macros don't do +// any registration. + +#define MEDIAPIPE_REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, \ + name, ...) +#define MEDIAPIPE_STATIC_REGISTRATOR_TEMPLATE(RegistratorName, RegistryType, \ + name, ...) \ + template <typename T> \ + class RegistratorName {}; + +#elif MEDIAPIPE_ENABLE_STATIC_REGISTRATION_DRY_RUN +// When static registration is enabled and running in Dry-Run mode, make sure +// corresponding macros print registration details instead of doing actual +// registration. + +#define INTERNAL_MEDIAPIPE_REGISTER_FACTORY_STRINGIFY_HELPER(x) #x +#define INTERNAL_MEDIAPIPE_REGISTER_FACTORY_STRINGIFY(x) \ + INTERNAL_MEDIAPIPE_REGISTER_FACTORY_STRINGIFY_HELPER(x) #define MEDIAPIPE_REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, \ name, ...) \ - static auto* REGISTRY_STATIC_VAR(var_name, __LINE__) = \ - new mediapipe::RegistrationToken( \ - RegistryType::Register(name, __VA_ARGS__)) + static mediapipe::RegistrationToken* REGISTRY_STATIC_VAR(var_name, \ + __LINE__) = []() { \ + ABSL_RAW_LOG(WARNING, "Registration Dry Run: %s", \ + INTERNAL_MEDIAPIPE_REGISTER_FACTORY_STRINGIFY( \ + RegistryType::Register(name, __VA_ARGS__))); \ + return nullptr; \ + }(); -// TODO: migrate to the above. -#define REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, name, ...) \ - static auto* REGISTRY_STATIC_VAR(var_name, __LINE__) = \ - new mediapipe::RegistrationToken( \ - RegistryType::Register(#name, __VA_ARGS__)) +#define MEDIAPIPE_STATIC_REGISTRATOR_TEMPLATE(RegistratorName, RegistryType, \ + names, ...) \ + template <typename T> \ + struct Internal##RegistratorName { \ + static NoDestructor<mediapipe::RegistrationToken> registration; \ + \ + static mediapipe::RegistrationToken Make() { \ + ABSL_RAW_LOG(WARNING, "Registration Dry Run: %s", \ + INTERNAL_MEDIAPIPE_REGISTER_FACTORY_STRINGIFY( \ + RegistryType::Register(names, __VA_ARGS__))); \ + ABSL_RAW_LOG(WARNING, "Where typeid(T).name() is: %s", \ + typeid(T).name()); \ + return {}; \ + } \ + \ + using RequireStatics = \ + registration_internal::ForceStaticInstantiation<®istration>; \ + }; \ + /* Static members of template classes can be defined in the header. */ \ + template <typename T> \ + NoDestructor<mediapipe::RegistrationToken> \ + Internal##RegistratorName<T>::registration( \ + Internal##RegistratorName<T>::Make()); \ + \ + template <typename T> \ + class RegistratorName { \ + private: \ + /* The member below triggers instantiation of the registration static. */ \ + typename Internal##RegistratorName<T>::RequireStatics register_; \ + }; + +#else +// When static registration is enabled and NOT running in Dry-Run mode, make +// sure corresponding macros do proper static registration. + +#define MEDIAPIPE_REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, \ + name, ...) \ + static mediapipe::RegistrationToken* REGISTRY_STATIC_VAR(var_name, \ + __LINE__) = \ + new mediapipe::RegistrationToken( \ + RegistryType::Register(name, __VA_ARGS__)); // Defines a utility registrator class which can be used to automatically // register factory functions. @@ -478,12 +570,21 @@ class RegistratorName { \ private: \ /* The member below triggers instantiation of the registration static. */ \ - /* Note that the constructor of calculator subclasses is only invoked */ \ - /* through the registration token, and so we cannot simply use the */ \ - /* static in theconstructor. */ \ typename Internal##RegistratorName<T>::RequireStatics register_; \ }; +#endif // MEDIAPIPE_DISABLE_STATIC_REGISTRATION + +#define MEDIAPIPE_REGISTER_FACTORY_FUNCTION(RegistryType, name, ...) \ + MEDIAPIPE_REGISTER_FACTORY_FUNCTION_QUALIFIED( \ + RegistryType, registration_##name, #name, __VA_ARGS__) + +// TODO: migrate usages to use +// MEDIAPIPE_REGISTER_FACTORY_FUNCTION_QUALIFIED. +#define REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, name, ...) \ + MEDIAPIPE_REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, #name, \ + __VA_ARGS__) + } // namespace mediapipe #endif // MEDIAPIPE_DEPS_REGISTRATION_H_
diff --git a/third_party/mediapipe/src/mediapipe/framework/deps/safe_int.h b/third_party/mediapipe/src/mediapipe/framework/deps/safe_int.h index 8b717cb..ec44aeff 100644 --- a/third_party/mediapipe/src/mediapipe/framework/deps/safe_int.h +++ b/third_party/mediapipe/src/mediapipe/framework/deps/safe_int.h
@@ -44,9 +44,9 @@ #include <limits> #include <type_traits> -#include "absl/log/absl_check.h" #include "mediapipe/framework/deps/strong_int.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace intops { @@ -68,8 +68,8 @@ // Check that the underlying integral type provides a range that is // compatible with two's complement. if (std::numeric_limits<T>::is_signed) { - ABSL_CHECK_EQ( - -1, std::numeric_limits<T>::min() + std::numeric_limits<T>::max()) + ABSL_CHECK_EQ(-1, + std::numeric_limits<T>::min() + std::numeric_limits<T>::max()) << "unexpected integral bounds"; }
diff --git a/third_party/mediapipe/src/mediapipe/framework/deps/threadpool_pthread_impl.cc b/third_party/mediapipe/src/mediapipe/framework/deps/threadpool_pthread_impl.cc index 9610f374..a36bf41 100644 --- a/third_party/mediapipe/src/mediapipe/framework/deps/threadpool_pthread_impl.cc +++ b/third_party/mediapipe/src/mediapipe/framework/deps/threadpool_pthread_impl.cc
@@ -18,11 +18,11 @@ #include <sys/syscall.h> #include <unistd.h> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "mediapipe/framework/deps/threadpool.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/framework/deps/topologicalsorter.cc b/third_party/mediapipe/src/mediapipe/framework/deps/topologicalsorter.cc index 65cd825..a8b3433 100644 --- a/third_party/mediapipe/src/mediapipe/framework/deps/topologicalsorter.cc +++ b/third_party/mediapipe/src/mediapipe/framework/deps/topologicalsorter.cc
@@ -16,8 +16,8 @@ #include <algorithm> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -27,7 +27,7 @@ } void TopologicalSorter::AddEdge(int from, int to) { - CHECK(!traversal_started_ && from < num_nodes_ && to < num_nodes_ && + ABSL_CHECK(!traversal_started_ && from < num_nodes_ && to < num_nodes_ && from >= 0 && to >= 0); adjacency_lists_[from].push_back(to); }
diff --git a/third_party/mediapipe/src/mediapipe/framework/deps/vector.h b/third_party/mediapipe/src/mediapipe/framework/deps/vector.h index 5775753..53a6967 100644 --- a/third_party/mediapipe/src/mediapipe/framework/deps/vector.h +++ b/third_party/mediapipe/src/mediapipe/framework/deps/vector.h
@@ -24,10 +24,10 @@ #include <limits> #include <type_traits> -#include "absl/log/absl_check.h" #include "absl/utility/utility.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" template <typename T> class Vector2;
diff --git a/third_party/mediapipe/src/mediapipe/framework/encode_binary_proto.bzl b/third_party/mediapipe/src/mediapipe/framework/encode_binary_proto.bzl index e849d97..e0e9ae68 100644 --- a/third_party/mediapipe/src/mediapipe/framework/encode_binary_proto.bzl +++ b/third_party/mediapipe/src/mediapipe/framework/encode_binary_proto.bzl
@@ -37,29 +37,33 @@ output: The desired name of the output file. Optional. """ +load("@bazel_skylib//lib:paths.bzl", "paths") + PROTOC = "@com_google_protobuf//:protoc" -def _canonicalize_proto_path_oss(all_protos, genfile_path): - """For the protos from external repository, canonicalize the proto path and the file name. +def _canonicalize_proto_path_oss(f): + if not f.root.path: + return struct( + proto_path = ".", + file_name = f.short_path, + ) - Returns: - Proto path list and proto source file list. - """ - proto_paths = [] - proto_file_names = [] - for s in all_protos.to_list(): - if s.path.startswith(genfile_path): - repo_name, _, file_name = s.path[len(genfile_path + "/external/"):].partition("/") + # `f.path` looks like "<genfiles>/external/<repo>/(_virtual_imports/<library>/)?<file_name>" + repo_name, _, file_name = f.path[len(paths.join(f.root.path, "external") + "/"):].partition("/") + if file_name.startswith("_virtual_imports/"): + # This is a virtual import; move "_virtual_imports/<library>" from `repo_name` to `file_name`. + repo_name = paths.join(repo_name, *file_name.split("/", 2)[:2]) + file_name = file_name.split("/", 2)[-1] + return struct( + proto_path = paths.join(f.root.path, "external", repo_name), + file_name = file_name, + ) - # handle virtual imports - if file_name.startswith("_virtual_imports"): - repo_name = repo_name + "/" + "/".join(file_name.split("/", 2)[:2]) - file_name = file_name.split("/", 2)[-1] - proto_paths.append(genfile_path + "/external/" + repo_name) - proto_file_names.append(file_name) - else: - proto_file_names.append(s.path) - return ([" --proto_path=" + path for path in proto_paths], proto_file_names) +def _map_root_path(f): + return _canonicalize_proto_path_oss(f).proto_path + +def _map_short_path(f): + return _canonicalize_proto_path_oss(f).file_name def _get_proto_provider(dep): """Get the provider for protocol buffers from a dependnecy. @@ -90,24 +94,35 @@ sibling = textpb, ) - path_list, file_list = _canonicalize_proto_path_oss(all_protos, ctx.genfiles_dir.path) + args = ctx.actions.args() + args.add(textpb) + args.add(binarypb) + args.add(ctx.executable._proto_compiler) + args.add(ctx.attr.message_type, format = "--encode=%s") + args.add("--proto_path=.") + args.add_all( + all_protos, + map_each = _map_root_path, + format_each = "--proto_path=%s", + uniquify = True, + ) + args.add_all( + all_protos, + map_each = _map_short_path, + uniquify = True, + ) # Note: the combination of absolute_paths and proto_path, as well as the exact # order of gendir before ., is needed for the proto compiler to resolve # import statements that reference proto files produced by a genrule. ctx.actions.run_shell( - tools = all_protos.to_list() + [textpb, ctx.executable._proto_compiler], - outputs = [binarypb], - command = " ".join( - [ - ctx.executable._proto_compiler.path, - "--encode=" + ctx.attr.message_type, - "--proto_path=" + ctx.genfiles_dir.path, - "--proto_path=" + ctx.bin_dir.path, - "--proto_path=.", - ] + path_list + file_list + - ["<", textpb.path, ">", binarypb.path], + tools = depset( + direct = [textpb, ctx.executable._proto_compiler], + transitive = [all_protos], ), + outputs = [binarypb], + command = "${@:3} < $1 > $2", + arguments = [args], mnemonic = "EncodeProto", )
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/frame_buffer.h b/third_party/mediapipe/src/mediapipe/framework/formats/frame_buffer.h index 4ebf3c6..72796fa2 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/frame_buffer.h +++ b/third_party/mediapipe/src/mediapipe/framework/formats/frame_buffer.h
@@ -18,10 +18,10 @@ #include <vector> -#include "absl/log/absl_check.h" #include "absl/log/check.h" #include "absl/status/statusor.h" #include "mediapipe/framework/port/integral_types.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/image_frame.cc b/third_party/mediapipe/src/mediapipe/framework/formats/image_frame.cc index 458d130..535e3676 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/image_frame.cc +++ b/third_party/mediapipe/src/mediapipe/framework/formats/image_frame.cc
@@ -23,12 +23,12 @@ #include <algorithm> #include <utility> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "mediapipe/framework/formats/image_format.pb.h" #include "mediapipe/framework/port/aligned_malloc_and_free.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/proto_ns.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -100,7 +100,7 @@ width_ = width; height_ = height; ABSL_CHECK_NE(ImageFormat::UNKNOWN, format_); - CHECK(IsValidAlignmentNumber(alignment_boundary)); + ABSL_CHECK(IsValidAlignmentNumber(alignment_boundary)); width_step_ = width * NumberOfChannels() * ByteDepth(); if (alignment_boundary == 1) { pixel_data_ = {new uint8_t[height * width_step_], @@ -223,7 +223,7 @@ } bool ImageFrame::IsAligned(uint32_t alignment_boundary) const { - CHECK(IsValidAlignmentNumber(alignment_boundary)); + ABSL_CHECK(IsValidAlignmentNumber(alignment_boundary)); if (!pixel_data_) { return false; } @@ -383,7 +383,7 @@ } void ImageFrame::CopyToBuffer(uint8_t* buffer, int buffer_size) const { - CHECK(buffer); + ABSL_CHECK(buffer); ABSL_CHECK_EQ(1, ByteDepth()); const int data_size = width_ * height_ * NumberOfChannels(); ABSL_CHECK_LE(data_size, buffer_size); @@ -398,7 +398,7 @@ } void ImageFrame::CopyToBuffer(uint16_t* buffer, int buffer_size) const { - CHECK(buffer); + ABSL_CHECK(buffer); ABSL_CHECK_EQ(2, ByteDepth()); const int data_size = width_ * height_ * NumberOfChannels(); ABSL_CHECK_LE(data_size, buffer_size); @@ -413,7 +413,7 @@ } void ImageFrame::CopyToBuffer(float* buffer, int buffer_size) const { - CHECK(buffer); + ABSL_CHECK(buffer); ABSL_CHECK_EQ(4, ByteDepth()); const int data_size = width_ * height_ * NumberOfChannels(); ABSL_CHECK_LE(data_size, buffer_size);
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/image_multi_pool.cc b/third_party/mediapipe/src/mediapipe/framework/formats/image_multi_pool.cc index 5953d0f..1288982 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/image_multi_pool.cc +++ b/third_party/mediapipe/src/mediapipe/framework/formats/image_multi_pool.cc
@@ -16,7 +16,6 @@ #include <tuple> -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/port/logging.h" @@ -25,6 +24,7 @@ #ifdef __APPLE__ #include "mediapipe/objc/CFHolder.h" #include "mediapipe/objc/util.h" +#include "absl/log/absl_check.h" #endif // __APPLE__ #endif // !MEDIAPIPE_DISABLE_GPU @@ -66,7 +66,7 @@ CVPixelBufferRef buffer; CVReturn err = mediapipe::CreateCVPixelBufferWithoutPool( spec.width, spec.height, cv_format, &buffer); - CHECK(!err) << "Error creating pixel buffer: " << err; + ABSL_CHECK(!err) << "Error creating pixel buffer: " << err; return Image(MakeCFHolderAdopting(buffer)); #else CVPixelBufferRef buffer; @@ -88,7 +88,7 @@ } }, &buffer); - CHECK(!err) << "Error creating pixel buffer: " << err; + ABSL_CHECK(!err) << "Error creating pixel buffer: " << err; return Image(MakeCFHolderAdopting(buffer)); #endif // TARGET_IPHONE_SIMULATOR } @@ -200,7 +200,7 @@ void ImageMultiPool::RegisterTextureCache(mediapipe::CVTextureCacheType cache) { absl::MutexLock lock(&mutex_gpu_); - CHECK(std::find(texture_caches_.begin(), texture_caches_.end(), cache) == + ABSL_CHECK(std::find(texture_caches_.begin(), texture_caches_.end(), cache) == texture_caches_.end()) << "Attempting to register a texture cache twice"; texture_caches_.emplace_back(cache); @@ -211,7 +211,7 @@ absl::MutexLock lock(&mutex_gpu_); auto it = std::find(texture_caches_.begin(), texture_caches_.end(), cache); - CHECK(it != texture_caches_.end()) + ABSL_CHECK(it != texture_caches_.end()) << "Attempting to unregister an unknown texture cache"; texture_caches_.erase(it); }
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/image_opencv.cc b/third_party/mediapipe/src/mediapipe/framework/formats/image_opencv.cc index 498c783..9e066cf5 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/image_opencv.cc +++ b/third_party/mediapipe/src/mediapipe/framework/formats/image_opencv.cc
@@ -16,6 +16,7 @@ #include "mediapipe/framework/formats/image_format.pb.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace { // Maps Image format to OpenCV Mat type. @@ -100,7 +101,7 @@ auto owner = std::make_shared<MatWithPixelLock>(const_cast<mediapipe::Image*>(image)); uint8_t* data_ptr = owner->lock.Pixels(); - CHECK(data_ptr != nullptr); + ABSL_CHECK(data_ptr != nullptr); // Use Image to initialize in-place. Image still owns memory. if (steps[0] == sizes[1] * image->channels() * ImageFrame::ByteDepthForFormat(image->image_format())) {
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/location.cc b/third_party/mediapipe/src/mediapipe/framework/formats/location.cc index f2d7be0f..afae2cd 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/location.cc +++ b/third_party/mediapipe/src/mediapipe/framework/formats/location.cc
@@ -18,7 +18,6 @@ #include <cmath> #include <memory> -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/strings/substitute.h" #include "mediapipe/framework/formats/annotation/locus.pb.h" @@ -32,6 +31,7 @@ #include "mediapipe/framework/port/statusor.h" #include "mediapipe/framework/tool/status_util.h" #include "mediapipe/framework/type_map.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -40,7 +40,7 @@ // the location_data, the tightest bounding box, that contains all pixels // encoded in the rasterizations. Rectangle_i MaskToRectangle(const LocationData& location_data) { - CHECK(location_data.mask().has_rasterization()); + ABSL_CHECK(location_data.mask().has_rasterization()); const auto& rasterization = location_data.mask().rasterization(); if (rasterization.interval_size() == 0) { return Rectangle_i(0, 0, 0, 0); @@ -64,7 +64,7 @@ Location::Location(const LocationData& location_data) : location_data_(location_data) { - CHECK(IsValidLocationData(location_data_)); + ABSL_CHECK(IsValidLocationData(location_data_)); } Location Location::CreateGlobalLocation() { @@ -159,7 +159,7 @@ } Location& Location::Scale(const float scale) { - CHECK(!location_data_.has_mask()) + ABSL_CHECK(!location_data_.has_mask()) << "Location mask scaling is not implemented."; ABSL_CHECK_GT(scale, 0.0f); switch (location_data_.format()) {
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/location_opencv.cc b/third_party/mediapipe/src/mediapipe/framework/formats/location_opencv.cc index 90cd2bb..6c41b83a 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/location_opencv.cc +++ b/third_party/mediapipe/src/mediapipe/framework/formats/location_opencv.cc
@@ -14,7 +14,6 @@ #include "mediapipe/framework/formats/location_opencv.h" -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/strings/substitute.h" #include "mediapipe/framework/formats/annotation/rasterization.pb.h" @@ -22,12 +21,13 @@ #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/opencv_imgproc_inc.h" #include "mediapipe/framework/port/statusor.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace { Rectangle_i MaskToRectangle(const LocationData& location_data) { - CHECK(location_data.mask().has_rasterization()); + ABSL_CHECK(location_data.mask().has_rasterization()); const auto& rasterization = location_data.mask().rasterization(); if (rasterization.interval_size() == 0) { return Rectangle_i(0, 0, 0, 0);
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/matrix.cc b/third_party/mediapipe/src/mediapipe/framework/formats/matrix.cc index daad9c0..f4e2bb8 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/matrix.cc +++ b/third_party/mediapipe/src/mediapipe/framework/formats/matrix.cc
@@ -15,11 +15,11 @@ #include <algorithm> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/core_proto_inc.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/proto_ns.h" #include "mediapipe/framework/port/ret_check.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -35,7 +35,7 @@ void MatrixFromMatrixDataProto(const MatrixData& matrix_data, Matrix* matrix) { ABSL_CHECK_EQ(matrix_data.rows() * matrix_data.cols(), - matrix_data.packed_data_size()); + matrix_data.packed_data_size()); if (matrix_data.layout() == MatrixData::ROW_MAJOR) { matrix->resize(matrix_data.cols(), matrix_data.rows()); } else { @@ -57,9 +57,9 @@ } void MatrixFromTextProto(const std::string& text_proto, Matrix* matrix) { - CHECK(matrix); + ABSL_CHECK(matrix); MatrixData matrix_data; - CHECK(proto_ns::TextFormat::ParseFromString(text_proto, &matrix_data)); + ABSL_CHECK(proto_ns::TextFormat::ParseFromString(text_proto, &matrix_data)); MatrixFromMatrixDataProto(matrix_data, matrix); } #endif // !defined(MEDIAPIPE_MOBILE) && !defined(MEDIAPIPE_LITE)
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/motion/optical_flow_field.cc b/third_party/mediapipe/src/mediapipe/framework/formats/motion/optical_flow_field.cc index 8b3dd3f..8d8e173 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/motion/optical_flow_field.cc +++ b/third_party/mediapipe/src/mediapipe/framework/formats/motion/optical_flow_field.cc
@@ -18,7 +18,6 @@ #include <cmath> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "mediapipe/framework/deps/mathutil.h" @@ -30,6 +29,7 @@ #include "mediapipe/framework/port/point2.h" #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/type_map.h" +#include "absl/log/absl_check.h" namespace { @@ -41,8 +41,8 @@ void CartesianToPolarCoordinates(const cv::Mat& cartesian, cv::Mat* magnitudes, cv::Mat* angles) { - CHECK(magnitudes != nullptr); - CHECK(angles != nullptr); + ABSL_CHECK(magnitudes != nullptr); + ABSL_CHECK(angles != nullptr); cv::Mat cartesian_components[2]; cv::split(cartesian, cartesian_components); cv::cartToPolar(cartesian_components[0], cartesian_components[1], *magnitudes, @@ -192,8 +192,8 @@ bool OpticalFlowField::FollowFlow(float x, float y, float* new_x, float* new_y) const { - CHECK(new_x); - CHECK(new_y); + ABSL_CHECK(new_x); + ABSL_CHECK(new_y); if (x < 0 || x > flow_data_.cols - 1 || // horizontal bounds y < 0 || y > flow_data_.rows - 1) { // vertical bounds return false;
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/tensor.cc b/third_party/mediapipe/src/mediapipe/framework/formats/tensor.cc index 0445712..c1b8637 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/tensor.cc +++ b/third_party/mediapipe/src/mediapipe/framework/formats/tensor.cc
@@ -32,6 +32,7 @@ #include "mediapipe/framework/formats/tensor_mtl_buffer_view.h" #else #include <cstdlib> +#include "absl/log/absl_check.h" #endif // MEDIAPIPE_METAL_ENABLED namespace mediapipe { @@ -347,7 +348,7 @@ void* ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, bytes(), GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_WRITE_BIT); - CHECK(ptr) << "glMapBufferRange failed: " << glGetError(); + ABSL_CHECK(ptr) << "glMapBufferRange failed: " << glGetError(); std::memcpy(ptr, cpu_buffer_, bytes()); glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); } @@ -537,7 +538,7 @@ valid_ |= kValidCpu; return {ptr, std::move(lock), [ahwb = ahwb_] { auto error = AHardwareBuffer_unlock(ahwb, nullptr); - CHECK(error == 0) << "AHardwareBuffer_unlock " << error; + ABSL_CHECK(error == 0) << "AHardwareBuffer_unlock " << error; }}; } } @@ -621,7 +622,7 @@ if (ptr) { return {ptr, std::move(lock), [ahwb = ahwb_, fence_fd = &fence_fd_] { auto error = AHardwareBuffer_unlock(ahwb, fence_fd); - CHECK(error == 0) << "AHardwareBuffer_unlock " << error; + ABSL_CHECK(error == 0) << "AHardwareBuffer_unlock " << error; }}; } }
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/tensor.h b/third_party/mediapipe/src/mediapipe/framework/formats/tensor.h index 4f95eb2..87d11fd5 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/tensor.h +++ b/third_party/mediapipe/src/mediapipe/framework/formats/tensor.h
@@ -44,6 +44,7 @@ #if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30 #include "mediapipe/gpu/gl_base.h" #include "mediapipe/gpu/gl_context.h" +#include "absl/log/absl_check.h" #endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30 #if defined __has_builtin @@ -200,12 +201,12 @@ } int file_descriptor() const { return file_descriptor_; } void SetReadingFinishedFunc(FinishingFunc&& func) { - CHECK(ahwb_written_) + ABSL_CHECK(ahwb_written_) << "AHWB write view can't accept 'reading finished callback'"; *ahwb_written_ = std::move(func); } void SetWritingFinishedFD(int fd, FinishingFunc func = nullptr) { - CHECK(fence_fd_) + ABSL_CHECK(fence_fd_) << "AHWB read view can't accept 'writing finished file descriptor'"; *fence_fd_ = fd; *ahwb_written_ = std::move(func);
diff --git a/third_party/mediapipe/src/mediapipe/framework/formats/tensor_ahwb.cc b/third_party/mediapipe/src/mediapipe/framework/formats/tensor_ahwb.cc index 525f05f3..bf0de2a 100644 --- a/third_party/mediapipe/src/mediapipe/framework/formats/tensor_ahwb.cc +++ b/third_party/mediapipe/src/mediapipe/framework/formats/tensor_ahwb.cc
@@ -11,6 +11,7 @@ #include "mediapipe/framework/port.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/gpu/gl_base.h" +#include "absl/log/absl_check.h" #endif // MEDIAPIPE_TENSOR_USE_AHWB namespace mediapipe { @@ -208,12 +209,12 @@ Tensor::AHardwareBufferView Tensor::GetAHardwareBufferReadView() const { auto lock(absl::make_unique<absl::MutexLock>(&view_mutex_)); - CHECK(valid_ != kValidNone) << "Tensor must be written prior to read from."; - CHECK(!(valid_ & kValidOpenGlTexture2d)) + ABSL_CHECK(valid_ != kValidNone) << "Tensor must be written prior to read from."; + ABSL_CHECK(!(valid_ & kValidOpenGlTexture2d)) << "Tensor conversion between OpenGL texture and AHardwareBuffer is not " "supported."; bool transfer = !ahwb_; - CHECK(AllocateAHardwareBuffer()) + ABSL_CHECK(AllocateAHardwareBuffer()) << "AHardwareBuffer is not supported on the target system."; valid_ |= kValidAHardwareBuffer; if (transfer) { @@ -253,7 +254,7 @@ Tensor::AHardwareBufferView Tensor::GetAHardwareBufferWriteView( int size_alignment) const { auto lock(absl::make_unique<absl::MutexLock>(&view_mutex_)); - CHECK(AllocateAHardwareBuffer(size_alignment)) + ABSL_CHECK(AllocateAHardwareBuffer(size_alignment)) << "AHardwareBuffer is not supported on the target system."; valid_ = kValidAHardwareBuffer; return {ahwb_, @@ -319,7 +320,7 @@ if (__builtin_available(android 26, *)) { auto error = AHardwareBuffer_lock( ahwb_, AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY, -1, nullptr, &dest); - CHECK(error == 0) << "AHardwareBuffer_lock " << error; + ABSL_CHECK(error == 0) << "AHardwareBuffer_lock " << error; } if (valid_ & kValidCpu) { std::memcpy(dest, cpu_buffer_, bytes()); @@ -346,7 +347,7 @@ } if (__builtin_available(android 26, *)) { auto error = AHardwareBuffer_unlock(ahwb_, nullptr); - CHECK(error == 0) << "AHardwareBuffer_unlock " << error; + ABSL_CHECK(error == 0) << "AHardwareBuffer_unlock " << error; } } @@ -421,9 +422,9 @@ // TODO: Use tflite::gpu::GlBufferSync and GlActiveSync. gl_context_->Run([]() { glFinish(); }); } else if (valid_ & kValidAHardwareBuffer) { - CHECK(ahwb_written_) << "Ahwb-to-Cpu synchronization requires the " + ABSL_CHECK(ahwb_written_) << "Ahwb-to-Cpu synchronization requires the " "completion function to be set"; - CHECK(ahwb_written_(true)) + ABSL_CHECK(ahwb_written_(true)) << "An error oqcured while waiting for the buffer to be written"; } } @@ -431,7 +432,7 @@ auto error = AHardwareBuffer_lock(ahwb_, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, ssbo_written_, nullptr, &ptr); - CHECK(error == 0) << "AHardwareBuffer_lock " << error; + ABSL_CHECK(error == 0) << "AHardwareBuffer_lock " << error; close(ssbo_written_); ssbo_written_ = -1; return ptr; @@ -449,7 +450,7 @@ void* ptr; auto error = AHardwareBuffer_lock( ahwb_, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, nullptr, &ptr); - CHECK(error == 0) << "AHardwareBuffer_lock " << error; + ABSL_CHECK(error == 0) << "AHardwareBuffer_lock " << error; return ptr; } }
diff --git a/third_party/mediapipe/src/mediapipe/framework/graph_output_stream.cc b/third_party/mediapipe/src/mediapipe/framework/graph_output_stream.cc index 34b4ffea..290c14f 100644 --- a/third_party/mediapipe/src/mediapipe/framework/graph_output_stream.cc +++ b/third_party/mediapipe/src/mediapipe/framework/graph_output_stream.cc
@@ -14,9 +14,9 @@ #include "mediapipe/framework/graph_output_stream.h" -#include "absl/log/absl_check.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/port/status.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -154,7 +154,7 @@ } void OutputStreamPollerImpl::SetMaxQueueSize(int queue_size) { - CHECK(queue_size >= -1) + ABSL_CHECK(queue_size >= -1) << "Max queue size must be either -1 or non-negative."; input_stream_handler_->SetMaxQueueSize(queue_size); } @@ -176,7 +176,7 @@ } bool OutputStreamPollerImpl::Next(Packet* packet) { - CHECK(packet); + ABSL_CHECK(packet); bool empty_queue = true; bool timestamp_bound_changed = false; Timestamp min_timestamp = Timestamp::Unset();
diff --git a/third_party/mediapipe/src/mediapipe/framework/graph_service.h b/third_party/mediapipe/src/mediapipe/framework/graph_service.h index 12b2ccb..42b3ada 100644 --- a/third_party/mediapipe/src/mediapipe/framework/graph_service.h +++ b/third_party/mediapipe/src/mediapipe/framework/graph_service.h
@@ -22,6 +22,7 @@ #include "absl/strings/str_cat.h" #include "mediapipe/framework/packet.h" #include "mediapipe/framework/port/status.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -125,7 +126,7 @@ public: bool IsAvailable() { return service_ != nullptr; } T& GetObject() { - CHECK(service_) << "Service is unavailable."; + ABSL_CHECK(service_) << "Service is unavailable."; return *service_; }
diff --git a/third_party/mediapipe/src/mediapipe/framework/input_side_packet_handler.cc b/third_party/mediapipe/src/mediapipe/framework/input_side_packet_handler.cc index 9b01cc31..8e479dd 100644 --- a/third_party/mediapipe/src/mediapipe/framework/input_side_packet_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/input_side_packet_handler.cc
@@ -18,6 +18,7 @@ #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/status_builder.h" #include "mediapipe/framework/tool/fill_packet_set.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -82,7 +83,7 @@ void InputSidePacketHandler::TriggerErrorCallback( const absl::Status& status) const { - CHECK(error_callback_); + ABSL_CHECK(error_callback_); error_callback_(status); }
diff --git a/third_party/mediapipe/src/mediapipe/framework/input_stream_handler.cc b/third_party/mediapipe/src/mediapipe/framework/input_stream_handler.cc index 42830510..e365ac3 100644 --- a/third_party/mediapipe/src/mediapipe/framework/input_stream_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/input_stream_handler.cc
@@ -14,12 +14,12 @@ #include "mediapipe/framework/input_stream_handler.h" -#include "absl/log/absl_check.h" #include "absl/strings/str_join.h" #include "absl/strings/substitute.h" #include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/mediapipe_profiling.h" #include "mediapipe/framework/port/ret_check.h" +#include "absl/log/absl_check.h" namespace mediapipe { using SyncSet = InputStreamHandler::SyncSet; @@ -112,7 +112,7 @@ void InputStreamHandler::UpdateInputShardHeaders( InputStreamShardSet* input_shards) { - CHECK(input_shards); + ABSL_CHECK(input_shards); for (CollectionItemId id = input_stream_managers_.BeginId(); id < input_stream_managers_.EndId(); ++id) { input_shards->Get(id).SetHeader(input_stream_managers_.Get(id)->Header()); @@ -199,7 +199,7 @@ TraceEvent(TraceEvent::READY_FOR_PROCESS) .set_node_id(calculator_context->NodeId())); } else { - CHECK(node_readiness == NodeReadiness::kReadyForClose); + ABSL_CHECK(node_readiness == NodeReadiness::kReadyForClose); // If any parallel invocations are in progress or a calculator context has // been prepared for Close(), we shouldn't prepare another calculator // context for Close(). @@ -303,7 +303,7 @@ void InputStreamHandler::ClearCurrentInputs( CalculatorContext* calculator_context) { - CHECK(calculator_context); + ABSL_CHECK(calculator_context); calculator_context_manager_->PopInputTimestampFromContext(calculator_context); for (auto& input : calculator_context->Inputs()) { // Invokes InputStreamShard's private method to clear packet. @@ -318,20 +318,18 @@ } void InputStreamHandler::SetBatchSize(int batch_size) { - CHECK(!calculator_run_in_parallel_ || batch_size == 1) + ABSL_CHECK(!calculator_run_in_parallel_ || batch_size == 1) << "Batching cannot be combined with parallel execution."; - CHECK(!late_preparation_ || batch_size == 1) + ABSL_CHECK(!late_preparation_ || batch_size == 1) << "Batching cannot be combined with late preparation."; - ABSL_CHECK_GE(batch_size, 1) - << "Batch size has to be greater than or equal to 1."; + ABSL_CHECK_GE(batch_size, 1) << "Batch size has to be greater than or equal to 1."; // Source nodes shouldn't specify batch_size even if it's set to 1. - ABSL_CHECK_GE(NumInputStreams(), 0) - << "Source nodes cannot batch input packets."; + ABSL_CHECK_GE(NumInputStreams(), 0) << "Source nodes cannot batch input packets."; batch_size_ = batch_size; } void InputStreamHandler::SetLatePreparation(bool late_preparation) { - CHECK(batch_size_ == 1 || !late_preparation_) + ABSL_CHECK(batch_size_ == 1 || !late_preparation_) << "Batching cannot be combined with late preparation."; late_preparation_ = late_preparation; } @@ -407,8 +405,8 @@ void SyncSet::FillInputSet(Timestamp input_timestamp, InputStreamShardSet* input_set) { - CHECK(input_timestamp.IsAllowedInStream()); - CHECK(input_set); + ABSL_CHECK(input_timestamp.IsAllowedInStream()); + ABSL_CHECK(input_set); for (CollectionItemId id : stream_ids_) { const auto& stream = input_stream_handler_->input_stream_managers_.Get(id); int num_packets_dropped = 0;
diff --git a/third_party/mediapipe/src/mediapipe/framework/input_stream_manager.cc b/third_party/mediapipe/src/mediapipe/framework/input_stream_manager.cc index d38f3c6..01e93a3 100644 --- a/third_party/mediapipe/src/mediapipe/framework/input_stream_manager.cc +++ b/third_party/mediapipe/src/mediapipe/framework/input_stream_manager.cc
@@ -17,7 +17,6 @@ #include <type_traits> #include <utility> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/packet.h" @@ -25,6 +24,7 @@ #include "mediapipe/framework/port/source_location.h" #include "mediapipe/framework/port/status_builder.h" #include "mediapipe/framework/tool/status_util.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -245,7 +245,7 @@ Packet InputStreamManager::PopPacketAtTimestamp(Timestamp timestamp, int* num_packets_dropped, bool* stream_is_done) { - CHECK(enable_timestamps_); + ABSL_CHECK(enable_timestamps_); *num_packets_dropped = -1; *stream_is_done = false; bool queue_became_non_full = false; @@ -300,7 +300,7 @@ } Packet InputStreamManager::PopQueueHead(bool* stream_is_done) { - CHECK(!enable_timestamps_); + ABSL_CHECK(!enable_timestamps_); *stream_is_done = false; bool queue_became_non_full = false; Packet packet;
diff --git a/third_party/mediapipe/src/mediapipe/framework/input_stream_shard.cc b/third_party/mediapipe/src/mediapipe/framework/input_stream_shard.cc index 8e3348d..79c75d7 100644 --- a/third_party/mediapipe/src/mediapipe/framework/input_stream_shard.cc +++ b/third_party/mediapipe/src/mediapipe/framework/input_stream_shard.cc
@@ -13,13 +13,14 @@ // limitations under the License. #include "mediapipe/framework/input_stream_shard.h" +#include "absl/log/absl_check.h" namespace mediapipe { void InputStreamShard::AddPacket(Packet&& value, bool is_done) { // A packet can be added if the shard is still active or the packet being // added is empty. An empty packet corresponds to absence of a packet. - CHECK(!is_done_ || value.IsEmpty()); + ABSL_CHECK(!is_done_ || value.IsEmpty()); packet_queue_.emplace(std::move(value)); is_done_ = is_done; }
diff --git a/third_party/mediapipe/src/mediapipe/framework/legacy_calculator_support.h b/third_party/mediapipe/src/mediapipe/framework/legacy_calculator_support.h index 9378d14..6ec0d95 100644 --- a/third_party/mediapipe/src/mediapipe/framework/legacy_calculator_support.h +++ b/third_party/mediapipe/src/mediapipe/framework/legacy_calculator_support.h
@@ -66,7 +66,7 @@ }; }; -#if !defined(_MSC_VER) +#if !defined(_MSC_VER) || defined(__clang__) // We only declare this variable for two specializations of the template because // it is only meant to be used for these two types. // Note that, since these variables are members of specific template
diff --git a/third_party/mediapipe/src/mediapipe/framework/output_side_packet_impl.cc b/third_party/mediapipe/src/mediapipe/framework/output_side_packet_impl.cc index 94bc518..e6f9c6e 100644 --- a/third_party/mediapipe/src/mediapipe/framework/output_side_packet_impl.cc +++ b/third_party/mediapipe/src/mediapipe/framework/output_side_packet_impl.cc
@@ -17,6 +17,7 @@ #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/source_location.h" #include "mediapipe/framework/port/status_builder.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -42,7 +43,7 @@ void OutputSidePacketImpl::AddMirror( InputSidePacketHandler* input_side_packet_handler, CollectionItemId id) { - CHECK(input_side_packet_handler); + ABSL_CHECK(input_side_packet_handler); mirrors_.emplace_back(input_side_packet_handler, id); } @@ -81,7 +82,7 @@ void OutputSidePacketImpl::TriggerErrorCallback( const absl::Status& status) const { - CHECK(error_callback_); + ABSL_CHECK(error_callback_); error_callback_(status); }
diff --git a/third_party/mediapipe/src/mediapipe/framework/output_stream_handler.cc b/third_party/mediapipe/src/mediapipe/framework/output_stream_handler.cc index 74138cd7..705a1d4c 100644 --- a/third_party/mediapipe/src/mediapipe/framework/output_stream_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/output_stream_handler.cc
@@ -14,10 +14,10 @@ #include "mediapipe/framework/output_stream_handler.h" -#include "absl/log/absl_check.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/output_stream_shard.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -32,7 +32,7 @@ absl::Status OutputStreamHandler::SetupOutputShards( OutputStreamShardSet* output_shards) { - CHECK(output_shards); + ABSL_CHECK(output_shards); for (CollectionItemId id = output_stream_managers_.BeginId(); id < output_stream_managers_.EndId(); ++id) { OutputStreamManager* manager = output_stream_managers_.Get(id); @@ -53,7 +53,7 @@ } void OutputStreamHandler::Open(OutputStreamShardSet* output_shards) { - CHECK(output_shards); + ABSL_CHECK(output_shards); PropagateOutputPackets(Timestamp::Unstarted(), output_shards); for (auto& manager : output_stream_managers_) { manager->PropagateHeader(); @@ -63,7 +63,7 @@ void OutputStreamHandler::PrepareOutputs(Timestamp input_timestamp, OutputStreamShardSet* output_shards) { - CHECK(output_shards); + ABSL_CHECK(output_shards); for (CollectionItemId id = output_stream_managers_.BeginId(); id < output_stream_managers_.EndId(); ++id) { output_stream_managers_.Get(id)->ResetShard(&output_shards->Get(id)); @@ -150,7 +150,7 @@ void OutputStreamHandler::PropagateOutputPackets( Timestamp input_timestamp, OutputStreamShardSet* output_shards) { - CHECK(output_shards); + ABSL_CHECK(output_shards); for (CollectionItemId id = output_stream_managers_.BeginId(); id < output_stream_managers_.EndId(); ++id) { OutputStreamManager* manager = output_stream_managers_.Get(id);
diff --git a/third_party/mediapipe/src/mediapipe/framework/output_stream_handler.h b/third_party/mediapipe/src/mediapipe/framework/output_stream_handler.h index cb6b2d6..eae9597d 100644 --- a/third_party/mediapipe/src/mediapipe/framework/output_stream_handler.h +++ b/third_party/mediapipe/src/mediapipe/framework/output_stream_handler.h
@@ -25,7 +25,6 @@ // TODO: Move protos in another CL after the C++ code migration. #include "absl/base/thread_annotations.h" -#include "absl/log/absl_check.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/calculator_context_manager.h" #include "mediapipe/framework/collection.h" @@ -37,6 +36,7 @@ #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/timestamp.h" #include "mediapipe/framework/tool/tag_map.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/framework/output_stream_manager.cc b/third_party/mediapipe/src/mediapipe/framework/output_stream_manager.cc index b092313e..547d05c 100644 --- a/third_party/mediapipe/src/mediapipe/framework/output_stream_manager.cc +++ b/third_party/mediapipe/src/mediapipe/framework/output_stream_manager.cc
@@ -17,6 +17,7 @@ #include "absl/synchronization/mutex.h" #include "mediapipe/framework/input_stream_handler.h" #include "mediapipe/framework/port/status_builder.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -80,7 +81,7 @@ void OutputStreamManager::AddMirror(InputStreamHandler* input_stream_handler, CollectionItemId id) { - CHECK(input_stream_handler); + ABSL_CHECK(input_stream_handler); mirrors_.emplace_back(input_stream_handler, id); } @@ -163,7 +164,7 @@ // TODO Consider moving the propagation logic to OutputStreamHandler. void OutputStreamManager::PropagateUpdatesToMirrors( Timestamp next_timestamp_bound, OutputStreamShard* output_stream_shard) { - CHECK(output_stream_shard); + ABSL_CHECK(output_stream_shard); { if (next_timestamp_bound != Timestamp::Unset()) { absl::MutexLock lock(&stream_mutex_);
diff --git a/third_party/mediapipe/src/mediapipe/framework/output_stream_poller.h b/third_party/mediapipe/src/mediapipe/framework/output_stream_poller.h index 98ebda3..8945b20 100644 --- a/third_party/mediapipe/src/mediapipe/framework/output_stream_poller.h +++ b/third_party/mediapipe/src/mediapipe/framework/output_stream_poller.h
@@ -17,8 +17,8 @@ #include <memory> -#include "absl/log/absl_check.h" #include "mediapipe/framework/graph_output_stream.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/framework/output_stream_shard.cc b/third_party/mediapipe/src/mediapipe/framework/output_stream_shard.cc index 682c704c..e91c8608 100644 --- a/third_party/mediapipe/src/mediapipe/framework/output_stream_shard.cc +++ b/third_party/mediapipe/src/mediapipe/framework/output_stream_shard.cc
@@ -17,13 +17,14 @@ #include "mediapipe/framework/port/source_location.h" #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/port/status_builder.h" +#include "absl/log/absl_check.h" namespace mediapipe { OutputStreamShard::OutputStreamShard() : closed_(false) {} void OutputStreamShard::SetSpec(OutputStreamSpec* output_stream_spec) { - CHECK(output_stream_spec); + ABSL_CHECK(output_stream_spec); output_stream_spec_ = output_stream_spec; }
diff --git a/third_party/mediapipe/src/mediapipe/framework/output_stream_shard.h b/third_party/mediapipe/src/mediapipe/framework/output_stream_shard.h index 81a89759..e5cc3fb 100644 --- a/third_party/mediapipe/src/mediapipe/framework/output_stream_shard.h +++ b/third_party/mediapipe/src/mediapipe/framework/output_stream_shard.h
@@ -18,12 +18,12 @@ #include <list> #include <string> -#include "absl/log/absl_check.h" #include "mediapipe/framework/output_stream.h" #include "mediapipe/framework/packet.h" #include "mediapipe/framework/packet_type.h" #include "mediapipe/framework/port.h" #include "mediapipe/framework/timestamp.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/framework/packet.cc b/third_party/mediapipe/src/mediapipe/framework/packet.cc index 05d3c6c..6bd9a0e 100644 --- a/third_party/mediapipe/src/mediapipe/framework/packet.cc +++ b/third_party/mediapipe/src/mediapipe/framework/packet.cc
@@ -22,6 +22,7 @@ #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/port/status_builder.h" #include "mediapipe/framework/tool/type_util.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace packet_internal { @@ -135,9 +136,9 @@ } const proto_ns::MessageLite& Packet::GetProtoMessageLite() const { - CHECK(holder_ != nullptr) << "The packet is empty."; + ABSL_CHECK(holder_ != nullptr) << "The packet is empty."; const proto_ns::MessageLite* proto = holder_->GetProtoMessageLite(); - CHECK(proto != nullptr) << "The Packet stores '" << holder_->DebugTypeName() + ABSL_CHECK(proto != nullptr) << "The Packet stores '" << holder_->DebugTypeName() << "', it cannot be converted to MessageLite type."; return *proto; }
diff --git a/third_party/mediapipe/src/mediapipe/framework/packet.h b/third_party/mediapipe/src/mediapipe/framework/packet.h index 2739b09..5b8867a 100644 --- a/third_party/mediapipe/src/mediapipe/framework/packet.h +++ b/third_party/mediapipe/src/mediapipe/framework/packet.h
@@ -18,12 +18,12 @@ #define MEDIAPIPE_FRAMEWORK_PACKET_H_ #include <cstddef> +#include <cstdint> #include <memory> #include <string> #include <type_traits> #include "absl/base/macros.h" -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "absl/synchronization/mutex.h" @@ -40,6 +40,7 @@ #include "mediapipe/framework/timestamp.h" #include "mediapipe/framework/tool/type_util.h" #include "mediapipe/framework/type_map.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -369,11 +370,14 @@ } // Returns a printable string identifying the type stored in the holder. virtual const std::string DebugTypeName() const = 0; + // Returns debug data id. + virtual int64_t DebugDataId() const = 0; // Returns the registered type name if it's available, otherwise the // empty string. virtual const std::string RegisteredTypeName() const = 0; // Get the type id of the underlying data type. virtual TypeId GetTypeId() const = 0; + // Downcasts this to Holder<T>. Returns nullptr if deserialization // failed or if the requested type is not what is stored. template <typename T> @@ -452,60 +456,37 @@ !std::is_same<proto_ns::MessageLite, T>{} && !std::is_same<proto_ns::Message, T>{}> {}; -// Registers a message type. T must be a non-cv-qualified concrete proto type. template <typename T> -struct MessageRegistrationImpl { - static NoDestructor<mediapipe::RegistrationToken> registration; - // This could have been a lambda inside registration's initializer below, but - // MSVC has a bug with lambdas, so we put it here as a workaround. - static std::unique_ptr<Holder<T>> CreateMessageHolder() { - return absl::make_unique<Holder<T>>(new T); - } -}; +std::unique_ptr<HolderBase> CreateMessageHolder() { + return absl::make_unique<Holder<T>>(new T); +} -// Static members of template classes can be defined in the header. -template <typename T> -NoDestructor<mediapipe::RegistrationToken> - MessageRegistrationImpl<T>::registration(MessageHolderRegistry::Register( - T{}.GetTypeName(), MessageRegistrationImpl<T>::CreateMessageHolder)); +// Registers a message type. T must be a non-cv-qualified concrete proto type. +MEDIAPIPE_STATIC_REGISTRATOR_TEMPLATE(MessageRegistrator, MessageHolderRegistry, + T{}.GetTypeName(), CreateMessageHolder<T>) // For non-Message payloads, this does nothing. template <typename T, typename Enable = void> -struct HolderSupport { - static void EnsureStaticInit() {} -}; +struct HolderPayloadRegistrator {}; // This template ensures that, for each concrete MessageLite subclass that is // stored in a Packet, we register a function that allows us to create a // Holder with the correct payload type from the proto's type name. +// +// We must use std::remove_cv to ensure we don't try to register Foo twice if +// there are Holder<Foo> and Holder<const Foo>. TODO: lift this +// up to Holder? template <typename T> -struct HolderSupport<T, - typename std::enable_if<is_concrete_proto_t<T>{}>::type> { - // We must use std::remove_cv to ensure we don't try to register Foo twice if - // there are Holder<Foo> and Holder<const Foo>. TODO: lift this - // up to Holder? - using R = MessageRegistrationImpl<typename std::remove_cv<T>::type>; - // For the registration static member to be instantiated, it needs to be - // referenced in a context that requires the definition to exist (see ISO/IEC - // C++ 2003 standard, 14.7.1). Calling this ensures that's the case. - // We need two different call-sites to cover proto types for which packets - // are only ever created (i.e. the protos are only produced by calculators) - // and proto types for which packets are only ever consumed (i.e. the protos - // are only consumed by calculators). - static void EnsureStaticInit() { CHECK(R::registration.get() != nullptr); } -}; +struct HolderPayloadRegistrator< + T, typename std::enable_if<is_concrete_proto_t<T>{}>::type> + : private MessageRegistrator<typename std::remove_cv<T>::type> {}; template <typename T> -class Holder : public HolderBase { +class Holder : public HolderBase, private HolderPayloadRegistrator<T> { public: - explicit Holder(const T* ptr) : ptr_(ptr) { - HolderSupport<T>::EnsureStaticInit(); - } + explicit Holder(const T* ptr) : ptr_(ptr) {} ~Holder() override { delete_helper(); } - const T& data() const { - HolderSupport<T>::EnsureStaticInit(); - return *ptr_; - } + const T& data() const { return *ptr_; } TypeId GetTypeId() const final { return kTypeId<T>; } // Releases the underlying data pointer and transfers the ownership to a // unique pointer. @@ -535,6 +516,7 @@ const std::string DebugTypeName() const final { return MediaPipeTypeStringOrDemangled<T>(); } + int64_t DebugDataId() const final { return reinterpret_cast<int64_t>(ptr_); } const std::string RegisteredTypeName() const final { const std::string* type_string = MediaPipeTypeString<T>(); if (type_string) {
diff --git a/third_party/mediapipe/src/mediapipe/framework/packet_type.h b/third_party/mediapipe/src/mediapipe/framework/packet_type.h index d4a0438..0643e804e 100644 --- a/third_party/mediapipe/src/mediapipe/framework/packet_type.h +++ b/third_party/mediapipe/src/mediapipe/framework/packet_type.h
@@ -23,7 +23,6 @@ #include <vector> #include "absl/base/macros.h" -#include "absl/log/absl_check.h" #include "absl/status/status.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" @@ -36,6 +35,7 @@ #include "mediapipe/framework/tool/type_util.h" #include "mediapipe/framework/tool/validate_name.h" #include "mediapipe/framework/type_map.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -183,8 +183,8 @@ // This function can only be called if HasError() is true. const std::vector<std::string>& ErrorMessages() const { ABSL_CHECK(missing_) << "ErrorMessages() can only be called if errors have " - "occurred. Call HasError() before calling this " - "function."; + "occurred. Call HasError() before calling this " + "function."; if (!missing_->initialized_errors) { for (const auto& entry : missing_->entries) { // Optional entries that were missing are not considered errors.
diff --git a/third_party/mediapipe/src/mediapipe/framework/port/parse_text_proto.h b/third_party/mediapipe/src/mediapipe/framework/port/parse_text_proto.h index c352d4f..ad345ec 100644 --- a/third_party/mediapipe/src/mediapipe/framework/port/parse_text_proto.h +++ b/third_party/mediapipe/src/mediapipe/framework/port/parse_text_proto.h
@@ -18,6 +18,7 @@ #include "mediapipe/framework/port/core_proto_inc.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/proto_ns.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -29,7 +30,7 @@ template <typename T> T ParseTextProtoOrDie(const std::string& input) { T result; - CHECK(ParseTextProto(input, &result)); + ABSL_CHECK(ParseTextProto(input, &result)); return result; }
diff --git a/third_party/mediapipe/src/mediapipe/framework/profiler/graph_profiler.cc b/third_party/mediapipe/src/mediapipe/framework/profiler/graph_profiler.cc index 6aead52..4b68445 100644 --- a/third_party/mediapipe/src/mediapipe/framework/profiler/graph_profiler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/profiler/graph_profiler.cc
@@ -32,6 +32,7 @@ #include "mediapipe/framework/tool/name_util.h" #include "mediapipe/framework/tool/tag_map.h" #include "mediapipe/framework/tool/validate_name.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -158,7 +159,7 @@ const ValidatedGraphConfig& validated_graph_config) { absl::WriterMutexLock lock(&profiler_mutex_); validated_graph_ = &validated_graph_config; - CHECK(!is_initialized_) + ABSL_CHECK(!is_initialized_) << "Cannot initialize the profiler for the same graph multiple times."; profiler_config_ = validated_graph_config.Config().profiler_config(); int64 interval_size_usec = profiler_config_.histogram_interval_size_usec(); @@ -190,7 +191,7 @@ } auto iter = calculator_profiles_.insert({node_name, profile}); - CHECK(iter.second) << absl::Substitute( + ABSL_CHECK(iter.second) << absl::Substitute( "Calculator \"$0\" has already been added.", node_name); } profile_builder_ = std::make_unique<GraphProfileBuilder>(this); @@ -201,7 +202,7 @@ void GraphProfiler::SetClock(const std::shared_ptr<mediapipe::Clock>& clock) { absl::WriterMutexLock lock(&profiler_mutex_); - CHECK(clock) << "GraphProfiler::SetClock() is called with a nullptr."; + ABSL_CHECK(clock) << "GraphProfiler::SetClock() is called with a nullptr."; clock_ = clock; } @@ -386,7 +387,7 @@ tool::ParseTagIndex(input_stream_info.tag_index(), &tag, &index)) << absl::Substitute("Cannot parse TAG or index for the backedge \"$0\"", input_stream_info.tag_index()); - CHECK(0 <= index && index < input_tag_map.NumEntries(tag)) + ABSL_CHECK(0 <= index && index < input_tag_map.NumEntries(tag)) << absl::Substitute( "The input_stream_info for tag \"$0\" (index " "$1) does not match any input_stream.", @@ -445,7 +446,7 @@ const std::string& node_name = calculator_context.NodeName(); int64 time_usec = end_time_usec - start_time_usec; auto profile_iter = calculator_profiles_.find(node_name); - CHECK(profile_iter != calculator_profiles_.end()) << absl::Substitute( + ABSL_CHECK(profile_iter != calculator_profiles_.end()) << absl::Substitute( "Calculator \"$0\" has not been added during initialization.", calculator_context.NodeName()); CalculatorProfile* calculator_profile = &profile_iter->second; @@ -467,7 +468,7 @@ const std::string& node_name = calculator_context.NodeName(); int64 time_usec = end_time_usec - start_time_usec; auto profile_iter = calculator_profiles_.find(node_name); - CHECK(profile_iter != calculator_profiles_.end()) << absl::Substitute( + ABSL_CHECK(profile_iter != calculator_profiles_.end()) << absl::Substitute( "Calculator \"$0\" has not been added during initialization.", calculator_context.NodeName()); CalculatorProfile* calculator_profile = &profile_iter->second; @@ -545,7 +546,7 @@ const std::string& node_name = calculator_context.NodeName(); auto profile_iter = calculator_profiles_.find(node_name); - CHECK(profile_iter != calculator_profiles_.end()) << absl::Substitute( + ABSL_CHECK(profile_iter != calculator_profiles_.end()) << absl::Substitute( "Calculator \"$0\" has not been added during initialization.", calculator_context.NodeName()); CalculatorProfile* calculator_profile = &profile_iter->second;
diff --git a/third_party/mediapipe/src/mediapipe/framework/profiler/trace_buffer.h b/third_party/mediapipe/src/mediapipe/framework/profiler/trace_buffer.h index b5e2d999..8dc09aef 100644 --- a/third_party/mediapipe/src/mediapipe/framework/profiler/trace_buffer.h +++ b/third_party/mediapipe/src/mediapipe/framework/profiler/trace_buffer.h
@@ -15,6 +15,9 @@ #ifndef MEDIAPIPE_FRAMEWORK_PROFILER_TRACE_BUFFER_H_ #define MEDIAPIPE_FRAMEWORK_PROFILER_TRACE_BUFFER_H_ +#include <cstdint> +#include <string> + #include "absl/time/time.h" #include "mediapipe/framework/calculator_profile.pb.h" #include "mediapipe/framework/packet.h" @@ -23,17 +26,6 @@ namespace mediapipe { -namespace packet_internal { -// Returns a hash of the packet data address from a packet data holder. -inline const int64 GetPacketDataId(const HolderBase* holder) { - if (holder == nullptr) { - return 0; - } - const void* address = &(static_cast<const Holder<int>*>(holder)->data()); - return reinterpret_cast<int64>(address); -} -} // namespace packet_internal - // Packet trace log event. struct TraceEvent { using EventType = GraphTrace::EventType; @@ -75,8 +67,12 @@ return *this; } inline TraceEvent& set_packet_data_id(const Packet* packet) { - this->event_data = - packet_internal::GetPacketDataId(packet_internal::GetHolder(*packet)); + const auto* holder = packet_internal::GetHolder(*packet); + int64_t data_id = 0; + if (holder != nullptr) { + data_id = holder->DebugDataId(); + } + this->event_data = data_id; return *this; } inline TraceEvent& set_thread_id(int thread_id) {
diff --git a/third_party/mediapipe/src/mediapipe/framework/scheduler.cc b/third_party/mediapipe/src/mediapipe/framework/scheduler.cc index f281ce4..5a26a00 100644 --- a/third_party/mediapipe/src/mediapipe/framework/scheduler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/scheduler.cc
@@ -19,7 +19,6 @@ #include <utility> #include <vector> -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/calculator_graph.h" @@ -31,6 +30,7 @@ #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/timestamp.h" #include "mediapipe/framework/tool/status_util.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -148,7 +148,7 @@ // Note: TryToScheduleNextSourceLayer unlocks and locks state_mutex_ // internally. bool did_activate = TryToScheduleNextSourceLayer(); - CHECK(did_activate || active_sources_.empty()); + ABSL_CHECK(did_activate || active_sources_.empty()); continue; } @@ -184,7 +184,7 @@ void Scheduler::Quit() { // All calls to Calculator::Process() have returned (even if we had an // error). - CHECK(state_ == STATE_RUNNING || state_ == STATE_CANCELLING); + ABSL_CHECK(state_ == STATE_RUNNING || state_ == STATE_CANCELLING); SetQueuesRunning(false); shared_.timer.EndRun(); @@ -271,13 +271,6 @@ return observed ? absl::OkStatus() : absl::OutOfRangeError("Graph is done."); } -// Idleness requires: -// 1. either the graph has no source nodes or all source nodes are closed, and -// 2. no packets are added to graph input streams. -// For simplicity, we only fully support WaitUntilIdle() to be called on a graph -// with no source nodes. -// The application must ensure no other threads are adding packets to graph -// input streams while a WaitUntilIdle() call is in progress. absl::Status Scheduler::WaitUntilIdle() { RET_CHECK_NE(state_, STATE_NOT_STARTED); ApplicationThreadAwait(std::bind(&Scheduler::IsIdle, this)); @@ -334,15 +327,15 @@ // container. void Scheduler::ScheduleNodeIfNotThrottled( CalculatorNode* node, CalculatorContext* calculator_context) { - DCHECK(node); - DCHECK(calculator_context); + ABSL_DCHECK(node); + ABSL_DCHECK(calculator_context); if (!graph_->IsNodeThrottled(node->Id())) { node->GetSchedulerQueue()->AddNode(node, calculator_context); } } void Scheduler::ScheduleNodeForOpen(CalculatorNode* node) { - DCHECK(node); + ABSL_DCHECK(node); VLOG(1) << "Scheduling OpenNode of calculator " << node->DebugName(); node->GetSchedulerQueue()->AddNodeForOpen(node); } @@ -352,7 +345,7 @@ for (CalculatorNode* node : nodes_to_schedule) { // Source nodes always reuse the default calculator context because they // can't be executed in parallel. - CHECK(node->IsSource()); + ABSL_CHECK(node->IsSource()); CalculatorContext* default_context = node->GetDefaultCalculatorContext(); node->GetSchedulerQueue()->AddNode(node, default_context); } @@ -375,8 +368,8 @@ bool Scheduler::TryToScheduleNextSourceLayer() { VLOG(3) << "TryToScheduleNextSourceLayer"; - CHECK(active_sources_.empty()); - CHECK(!sources_queue_.empty()); + ABSL_CHECK(active_sources_.empty()); + ABSL_CHECK(!sources_queue_.empty()); if (!unopened_sources_.empty() && (*unopened_sources_.begin())->source_layer() < @@ -428,9 +421,8 @@ } void Scheduler::AddUnopenedSourceNode(CalculatorNode* node) { - ABSL_CHECK_EQ(state_, STATE_NOT_STARTED) - << "AddUnopenedSourceNode can only be " - "called before starting the scheduler"; + ABSL_CHECK_EQ(state_, STATE_NOT_STARTED) << "AddUnopenedSourceNode can only be " + "called before starting the scheduler"; unopened_sources_.insert(node); } @@ -447,7 +439,7 @@ SchedulerQueue* queue; if (!node->Executor().empty()) { auto iter = non_default_queues_.find(node->Executor()); - CHECK(iter != non_default_queues_.end()); + ABSL_CHECK(iter != non_default_queues_.end()); queue = iter->second.get(); } else { queue = &default_queue_; @@ -530,7 +522,7 @@ while (!sources_queue_.empty()) { sources_queue_.pop(); } - CHECK(app_thread_tasks_.empty()); + ABSL_CHECK(app_thread_tasks_.empty()); } for (auto queue : scheduler_queues_) { queue->CleanupAfterRun();
diff --git a/third_party/mediapipe/src/mediapipe/framework/scheduler.h b/third_party/mediapipe/src/mediapipe/framework/scheduler.h index 8a6d079e..22d552c 100644 --- a/third_party/mediapipe/src/mediapipe/framework/scheduler.h +++ b/third_party/mediapipe/src/mediapipe/framework/scheduler.h
@@ -76,6 +76,16 @@ // be scheduled and nothing is running in the worker threads. This function // can be called only after Start(). // Runs application thread tasks while waiting. + // + // Idleness requires: + // 1. either the graph has no source nodes or all source nodes are closed, and + // 2. no packets are added to graph input streams. + // + // For simplicity, we only fully support WaitUntilIdle() to be called on a + // graph with no source nodes. + // + // The application must ensure no other threads are adding packets to graph + // input streams while a WaitUntilIdle() call is in progress. absl::Status WaitUntilIdle() ABSL_LOCKS_EXCLUDED(state_mutex_); // Wait until any graph input stream has been unthrottled.
diff --git a/third_party/mediapipe/src/mediapipe/framework/scheduler_queue.cc b/third_party/mediapipe/src/mediapipe/framework/scheduler_queue.cc index 08360ec..a55628f4 100644 --- a/third_party/mediapipe/src/mediapipe/framework/scheduler_queue.cc +++ b/third_party/mediapipe/src/mediapipe/framework/scheduler_queue.cc
@@ -18,13 +18,13 @@ #include <queue> #include <utility> -#include "absl/log/absl_check.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/calculator_node.h" #include "mediapipe/framework/executor.h" #include "mediapipe/framework/port/canonical_errors.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/status.h" +#include "absl/log/absl_check.h" #ifdef __APPLE__ #define AUTORELEASEPOOL @autoreleasepool @@ -37,8 +37,8 @@ SchedulerQueue::Item::Item(CalculatorNode* node, CalculatorContext* cc) : node_(node), cc_(cc) { - CHECK(node); - CHECK(cc); + ABSL_CHECK(node); + ABSL_CHECK(cc); is_source_ = node->IsSource(); id_ = node->Id(); if (is_source_) { @@ -49,7 +49,7 @@ SchedulerQueue::Item::Item(CalculatorNode* node) : node_(node), cc_(nullptr), is_open_node_(true) { - CHECK(node); + ABSL_CHECK(node); is_source_ = node->IsSource(); id_ = node->Id(); if (is_source_) { @@ -118,7 +118,7 @@ // Only happens when the framework tries to schedule an unthrottled source // node while it's running. For non-source nodes, if a calculator context is // prepared, it is committed to be scheduled. - CHECK(node->IsSource()) << node->DebugName(); + ABSL_CHECK(node->IsSource()) << node->DebugName(); return; } AddItemToQueue(Item(node, cc)); @@ -193,7 +193,7 @@ { absl::MutexLock lock(&mutex_); - CHECK(!queue_.empty()) << "Called RunNextTask when the queue is empty. " + ABSL_CHECK(!queue_.empty()) << "Called RunNextTask when the queue is empty. " "This should not happen."; node = queue_.top().Node(); @@ -201,7 +201,7 @@ is_open_node = queue_.top().IsOpenNode(); queue_.pop(); - CHECK(!node->Closed()) + ABSL_CHECK(!node->Closed()) << "Scheduled a node that was closed. This should not happen."; } @@ -212,7 +212,7 @@ // do it here to ensure all executors are covered. AUTORELEASEPOOL { if (is_open_node) { - DCHECK(!calculator_context); + ABSL_DCHECK(!calculator_context); OpenCalculatorNode(node); } else { RunCalculatorNode(node, calculator_context); @@ -267,7 +267,7 @@ // that all sources will be closed and no further sources should be // scheduled. The graph will be terminated as soon as its scheduler // queue becomes empty. - CHECK(!node->IsSource()); // ProcessNode takes care of StatusStop() + ABSL_CHECK(!node->IsSource()); // ProcessNode takes care of StatusStop() // from sources. shared_->stopping = true; } else {
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/BUILD b/third_party/mediapipe/src/mediapipe/framework/stream_handler/BUILD index 8b54ade8..6767a95 100644 --- a/third_party/mediapipe/src/mediapipe/framework/stream_handler/BUILD +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/BUILD
@@ -53,8 +53,16 @@ cc_library( name = "barrier_input_stream_handler", srcs = ["barrier_input_stream_handler.cc"], + hdrs = ["barrier_input_stream_handler.h"], deps = [ + "//mediapipe/framework:calculator_context_manager", + "//mediapipe/framework:calculator_framework", + "//mediapipe/framework:collection_item_id", "//mediapipe/framework:input_stream_handler", + "//mediapipe/framework:mediapipe_options_cc_proto", + "//mediapipe/framework/tool:tag_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/status", ], alwayslink = 1, ) @@ -74,8 +82,15 @@ cc_library( name = "early_close_input_stream_handler", srcs = ["early_close_input_stream_handler.cc"], + hdrs = ["early_close_input_stream_handler.h"], deps = [ + "//mediapipe/framework:calculator_context_manager", + "//mediapipe/framework:calculator_framework", + "//mediapipe/framework:collection_item_id", "//mediapipe/framework:input_stream_handler", + "//mediapipe/framework:mediapipe_options_cc_proto", + "//mediapipe/framework/tool:tag_map", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/strings", ], alwayslink = 1, @@ -84,10 +99,21 @@ cc_library( name = "fixed_size_input_stream_handler", srcs = ["fixed_size_input_stream_handler.cc"], + hdrs = ["fixed_size_input_stream_handler.h"], deps = [ ":default_input_stream_handler", ":fixed_size_input_stream_handler_cc_proto", + "//mediapipe/framework:calculator_context_manager", + "//mediapipe/framework:calculator_framework", + "//mediapipe/framework:collection_item_id", "//mediapipe/framework:input_stream_handler", + "//mediapipe/framework:mediapipe_options_cc_proto", + "//mediapipe/framework:packet", + "//mediapipe/framework/tool:tag_map", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/synchronization", ], alwayslink = 1, ) @@ -95,8 +121,18 @@ cc_library( name = "immediate_input_stream_handler", srcs = ["immediate_input_stream_handler.cc"], + hdrs = ["immediate_input_stream_handler.h"], deps = [ + "//mediapipe/framework:calculator_context_manager", + "//mediapipe/framework:calculator_framework", + "//mediapipe/framework:collection_item_id", "//mediapipe/framework:input_stream_handler", + "//mediapipe/framework:mediapipe_options_cc_proto", + "//mediapipe/framework/tool:tag_map", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/status", + "@com_google_absl//absl/synchronization", ], alwayslink = 1, ) @@ -122,9 +158,13 @@ cc_library( name = "mux_input_stream_handler", srcs = ["mux_input_stream_handler.cc"], + hdrs = ["mux_input_stream_handler.h"], deps = [ + "//mediapipe/framework:calculator_context_manager", + "//mediapipe/framework:calculator_framework", + "//mediapipe/framework:collection_item_id", "//mediapipe/framework:input_stream_handler", - "//mediapipe/framework/port:logging", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", ], @@ -134,16 +174,22 @@ cc_library( name = "sync_set_input_stream_handler", srcs = ["sync_set_input_stream_handler.cc"], + hdrs = ["sync_set_input_stream_handler.h"], deps = [ ":sync_set_input_stream_handler_cc_proto", - "//mediapipe/framework:collection", + "//mediapipe/framework:calculator_context_manager", + "//mediapipe/framework:calculator_framework", "//mediapipe/framework:collection_item_id", "//mediapipe/framework:input_stream_handler", "//mediapipe/framework:mediapipe_options_cc_proto", "//mediapipe/framework:packet_set", "//mediapipe/framework:timestamp", + "//mediapipe/framework/port:map_util", + "//mediapipe/framework/port:status", "//mediapipe/framework/tool:tag_map", - "@com_google_absl//absl/strings", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/status", "@com_google_absl//absl/synchronization", ], alwayslink = 1, @@ -152,12 +198,19 @@ cc_library( name = "timestamp_align_input_stream_handler", srcs = ["timestamp_align_input_stream_handler.cc"], + hdrs = ["timestamp_align_input_stream_handler.h"], deps = [ ":timestamp_align_input_stream_handler_cc_proto", + "//mediapipe/framework:calculator_context_manager", + "//mediapipe/framework:calculator_framework", "//mediapipe/framework:collection_item_id", "//mediapipe/framework:input_stream_handler", + "//mediapipe/framework:mediapipe_options_cc_proto", "//mediapipe/framework:timestamp", "//mediapipe/framework/tool:validate_name", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/status", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", ],
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/barrier_input_stream_handler.cc b/third_party/mediapipe/src/mediapipe/framework/stream_handler/barrier_input_stream_handler.cc index 250d3da27..7b60ca54 100644 --- a/third_party/mediapipe/src/mediapipe/framework/stream_handler/barrier_input_stream_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/barrier_input_stream_handler.cc
@@ -11,85 +11,71 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include "mediapipe/framework/stream_handler/barrier_input_stream_handler.h" -#include <algorithm> -#include <memory> -#include <vector> +#include <functional> +#include <utility> -#include "absl/log/absl_check.h" +#include "absl/log/check.h" +#include "absl/status/status.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/input_stream_handler.h" +#include "absl/log/absl_check.h" namespace mediapipe { -// Implementation of an input stream handler that considers a node as ready for -// Process() if all input streams have a packet available. This implies it must -// consider a node as ready for Close() if any input stream is done. -class BarrierInputStreamHandler : public InputStreamHandler { - public: - BarrierInputStreamHandler() = delete; - BarrierInputStreamHandler( - std::shared_ptr<tool::TagMap> tag_map, - CalculatorContextManager* calculator_context_manager, - const MediaPipeOptions& options, bool calculator_run_in_parallel) - : InputStreamHandler(std::move(tag_map), calculator_context_manager, - options, calculator_run_in_parallel) {} - - void PrepareForRun( - std::function<void()> headers_ready_callback, - std::function<void()> notification_callback, - std::function<void(CalculatorContext*)> schedule_callback, - std::function<void(absl::Status)> error_callback) override { - InputStreamHandler::PrepareForRun( - std::move(headers_ready_callback), std::move(notification_callback), - std::move(schedule_callback), std::move(error_callback)); - for (auto& stream : input_stream_managers_) { - stream->DisableTimestamps(); - } +void BarrierInputStreamHandler::PrepareForRun( + std::function<void()> headers_ready_callback, + std::function<void()> notification_callback, + std::function<void(CalculatorContext*)> schedule_callback, + std::function<void(absl::Status)> error_callback) { + InputStreamHandler::PrepareForRun( + std::move(headers_ready_callback), std::move(notification_callback), + std::move(schedule_callback), std::move(error_callback)); + for (auto& stream : input_stream_managers_) { + stream->DisableTimestamps(); } +} - protected: - // In BarrierInputStreamHandler, a node is "ready" if: - // - any stream is done (need to call Close() in this case), or - // - all streams have a packet available. - NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override { - DCHECK(min_stream_timestamp); - *min_stream_timestamp = Timestamp::Done(); - bool all_available = true; - for (const auto& stream : input_stream_managers_) { - bool empty; - Timestamp stream_timestamp = stream->MinTimestampOrBound(&empty); - if (empty) { - if (stream_timestamp == Timestamp::Done()) { - *min_stream_timestamp = Timestamp::Done(); - return NodeReadiness::kReadyForClose; - } - all_available = false; +NodeReadiness BarrierInputStreamHandler::GetNodeReadiness( + Timestamp* min_stream_timestamp) { + ABSL_DCHECK(min_stream_timestamp); + *min_stream_timestamp = Timestamp::Done(); + bool all_available = true; + for (const auto& stream : input_stream_managers_) { + bool empty; + Timestamp stream_timestamp = stream->MinTimestampOrBound(&empty); + if (empty) { + if (stream_timestamp == Timestamp::Done()) { + *min_stream_timestamp = Timestamp::Done(); + return NodeReadiness::kReadyForClose; } - *min_stream_timestamp = std::min(*min_stream_timestamp, stream_timestamp); + all_available = false; } - - ABSL_CHECK_NE(*min_stream_timestamp, Timestamp::Done()); - if (all_available) { - return NodeReadiness::kReadyForProcess; - } - return NodeReadiness::kNotReady; + *min_stream_timestamp = std::min(*min_stream_timestamp, stream_timestamp); } - // Only invoked when associated GetNodeReadiness() returned kReadyForProcess. - void FillInputSet(Timestamp input_timestamp, - InputStreamShardSet* input_set) override { - CHECK(input_timestamp.IsAllowedInStream()); - CHECK(input_set); - for (CollectionItemId id = input_stream_managers_.BeginId(); - id < input_stream_managers_.EndId(); ++id) { - auto& stream = input_stream_managers_.Get(id); - bool stream_is_done = false; - Packet current_packet = stream->PopQueueHead(&stream_is_done); - AddPacketToShard(&input_set->Get(id), std::move(current_packet), - stream_is_done); - } + ABSL_CHECK_NE(*min_stream_timestamp, Timestamp::Done()); + if (all_available) { + return NodeReadiness::kReadyForProcess; } -}; + return NodeReadiness::kNotReady; +} + +void BarrierInputStreamHandler::FillInputSet(Timestamp input_timestamp, + InputStreamShardSet* input_set) { + ABSL_CHECK(input_timestamp.IsAllowedInStream()); + ABSL_CHECK(input_set); + for (CollectionItemId id = input_stream_managers_.BeginId(); + id < input_stream_managers_.EndId(); ++id) { + auto& stream = input_stream_managers_.Get(id); + bool stream_is_done = false; + Packet current_packet = stream->PopQueueHead(&stream_is_done); + AddPacketToShard(&input_set->Get(id), std::move(current_packet), + stream_is_done); + } +} REGISTER_INPUT_STREAM_HANDLER(BarrierInputStreamHandler);
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/barrier_input_stream_handler.h b/third_party/mediapipe/src/mediapipe/framework/stream_handler/barrier_input_stream_handler.h new file mode 100644 index 0000000..55a21d3 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/barrier_input_stream_handler.h
@@ -0,0 +1,64 @@ + +// Copyright 2023 The MediaPipe Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_BARRIER_INPUT_STREAM_HANDLER_H_ +#define MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_BARRIER_INPUT_STREAM_HANDLER_H_ + +#include <functional> +#include <memory> +#include <utility> + +#include "absl/status/status.h" +#include "mediapipe/framework/calculator_context_manager.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/input_stream_handler.h" +#include "mediapipe/framework/mediapipe_options.pb.h" +#include "mediapipe/framework/tool/tag_map.h" + +namespace mediapipe { + +// Implementation of an input stream handler that considers a node as ready for +// Process() if all input streams have a packet available. This implies it must +// consider a node as ready for Close() if any input stream is done. +class BarrierInputStreamHandler : public InputStreamHandler { + public: + BarrierInputStreamHandler() = delete; + BarrierInputStreamHandler( + std::shared_ptr<tool::TagMap> tag_map, + CalculatorContextManager* calculator_context_manager, + const mediapipe::MediaPipeOptions& options, + bool calculator_run_in_parallel) + : InputStreamHandler(std::move(tag_map), calculator_context_manager, + options, calculator_run_in_parallel) {} + + void PrepareForRun(std::function<void()> headers_ready_callback, + std::function<void()> notification_callback, + std::function<void(CalculatorContext*)> schedule_callback, + std::function<void(absl::Status)> error_callback) override; + + protected: + // In BarrierInputStreamHandler, a node is "ready" if: + // - any stream is done (need to call Close() in this case), or + // - all streams have a packet available. + NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override; + + // Only invoked when associated GetNodeReadiness() returned kReadyForProcess. + void FillInputSet(Timestamp input_timestamp, + InputStreamShardSet* input_set) override; +}; + +} // namespace mediapipe + +#endif // MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_BARRIER_INPUT_STREAM_HANDLER_H_
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/early_close_input_stream_handler.cc b/third_party/mediapipe/src/mediapipe/framework/stream_handler/early_close_input_stream_handler.cc index c0ec0b67..822c5d5 100644 --- a/third_party/mediapipe/src/mediapipe/framework/stream_handler/early_close_input_stream_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/early_close_input_stream_handler.cc
@@ -1,4 +1,4 @@ -// Copyright 2019 The MediaPipe Authors. +// Copyright 2023 The MediaPipe Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,82 +11,71 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include "mediapipe/framework/stream_handler/early_close_input_stream_handler.h" #include <algorithm> -#include <memory> -#include <vector> -#include "absl/log/absl_check.h" +#include "absl/log/check.h" #include "absl/strings/substitute.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/input_stream_handler.h" +#include "absl/log/absl_check.h" namespace mediapipe { -// Implementation of an input stream handler that considers a node as ready for -// Close() if any input stream is done. -class EarlyCloseInputStreamHandler : public InputStreamHandler { - public: - EarlyCloseInputStreamHandler() = delete; - EarlyCloseInputStreamHandler(std::shared_ptr<tool::TagMap> tag_map, - CalculatorContextManager* cc_manager, - const MediaPipeOptions& options, - bool calculator_run_in_parallel) - : InputStreamHandler(std::move(tag_map), cc_manager, options, - calculator_run_in_parallel) {} - - protected: - // In EarlyCloseInputStreamHandler, a node is "ready" if: - // - any stream is done (need to call Close() in this case), or - // - the minimum bound (over all empty streams) is greater than the smallest - // timestamp of any stream, which means we have received all the packets - // that will be available at the next timestamp. - NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override { - DCHECK(min_stream_timestamp); - *min_stream_timestamp = Timestamp::Done(); - Timestamp min_bound = Timestamp::Done(); - for (const auto& stream : input_stream_managers_) { - bool empty; - Timestamp stream_timestamp = stream->MinTimestampOrBound(&empty); - if (empty) { - if (stream_timestamp == Timestamp::Done()) { - *min_stream_timestamp = Timestamp::Done(); - return NodeReadiness::kReadyForClose; - } - min_bound = std::min(min_bound, stream_timestamp); +// In EarlyCloseInputStreamHandler, a node is "ready" if: +// - any stream is done (need to call Close() in this case), or +// - the minimum bound (over all empty streams) is greater than the smallest +// timestamp of any stream, which means we have received all the packets +// that will be available at the next timestamp. +NodeReadiness EarlyCloseInputStreamHandler::GetNodeReadiness( + Timestamp* min_stream_timestamp) { + ABSL_DCHECK(min_stream_timestamp); + *min_stream_timestamp = Timestamp::Done(); + Timestamp min_bound = Timestamp::Done(); + for (const auto& stream : input_stream_managers_) { + bool empty; + Timestamp stream_timestamp = stream->MinTimestampOrBound(&empty); + if (empty) { + if (stream_timestamp == Timestamp::Done()) { + *min_stream_timestamp = Timestamp::Done(); + return NodeReadiness::kReadyForClose; } - *min_stream_timestamp = std::min(*min_stream_timestamp, stream_timestamp); + min_bound = std::min(min_bound, stream_timestamp); } - - ABSL_CHECK_NE(*min_stream_timestamp, Timestamp::Done()); - - if (min_bound > *min_stream_timestamp) { - return NodeReadiness::kReadyForProcess; - } - - ABSL_CHECK_EQ(min_bound, *min_stream_timestamp); - return NodeReadiness::kNotReady; + *min_stream_timestamp = std::min(*min_stream_timestamp, stream_timestamp); } - // Only invoked when associated GetNodeReadiness() returned kReadyForProcess. - void FillInputSet(Timestamp input_timestamp, - InputStreamShardSet* input_set) override { - CHECK(input_timestamp.IsAllowedInStream()); - CHECK(input_set); - for (CollectionItemId id = input_stream_managers_.BeginId(); - id < input_stream_managers_.EndId(); ++id) { - auto& stream = input_stream_managers_.Get(id); - int num_packets_dropped = 0; - bool stream_is_done = false; - Packet current_packet = stream->PopPacketAtTimestamp( - input_timestamp, &num_packets_dropped, &stream_is_done); - ABSL_CHECK_EQ(num_packets_dropped, 0) - << absl::Substitute("Dropped $0 packet(s) on input stream \"$1\".", - num_packets_dropped, stream->Name()); - AddPacketToShard(&input_set->Get(id), std::move(current_packet), - stream_is_done); - } + ABSL_CHECK_NE(*min_stream_timestamp, Timestamp::Done()); + + if (min_bound > *min_stream_timestamp) { + return NodeReadiness::kReadyForProcess; } -}; + + ABSL_CHECK_EQ(min_bound, *min_stream_timestamp); + return NodeReadiness::kNotReady; +} + +// Only invoked when associated GetNodeReadiness() returned kReadyForProcess. +void EarlyCloseInputStreamHandler::FillInputSet( + Timestamp input_timestamp, InputStreamShardSet* input_set) { + ABSL_CHECK(input_timestamp.IsAllowedInStream()); + ABSL_CHECK(input_set); + for (CollectionItemId id = input_stream_managers_.BeginId(); + id < input_stream_managers_.EndId(); ++id) { + auto& stream = input_stream_managers_.Get(id); + int num_packets_dropped = 0; + bool stream_is_done = false; + Packet current_packet = stream->PopPacketAtTimestamp( + input_timestamp, &num_packets_dropped, &stream_is_done); + ABSL_CHECK_EQ(num_packets_dropped, 0) + << absl::Substitute("Dropped $0 packet(s) on input stream \"$1\".", + num_packets_dropped, stream->Name()); + AddPacketToShard(&input_set->Get(id), std::move(current_packet), + stream_is_done); + } +} REGISTER_INPUT_STREAM_HANDLER(EarlyCloseInputStreamHandler);
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/early_close_input_stream_handler.h b/third_party/mediapipe/src/mediapipe/framework/stream_handler/early_close_input_stream_handler.h new file mode 100644 index 0000000..081954e --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/early_close_input_stream_handler.h
@@ -0,0 +1,56 @@ +// Copyright 2023 The MediaPipe Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_EARLY_CLOSE_INPUT_STREAM_HANDLER_H_ +#define MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_EARLY_CLOSE_INPUT_STREAM_HANDLER_H_ + +#include <memory> +#include <utility> + +#include "mediapipe/framework/calculator_context_manager.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/input_stream_handler.h" +#include "mediapipe/framework/mediapipe_options.pb.h" +#include "mediapipe/framework/tool/tag_map.h" + +namespace mediapipe { + +// Implementation of an input stream handler that considers a node as ready for +// Close() if any input stream is done. +class EarlyCloseInputStreamHandler : public InputStreamHandler { + public: + EarlyCloseInputStreamHandler() = delete; + EarlyCloseInputStreamHandler(std::shared_ptr<tool::TagMap> tag_map, + CalculatorContextManager* cc_manager, + const mediapipe::MediaPipeOptions& options, + bool calculator_run_in_parallel) + : InputStreamHandler(std::move(tag_map), cc_manager, options, + calculator_run_in_parallel) {} + + protected: + // In EarlyCloseInputStreamHandler, a node is "ready" if: + // - any stream is done (need to call Close() in this case), or + // - the minimum bound (over all empty streams) is greater than the smallest + // timestamp of any stream, which means we have received all the packets + // that will be available at the next timestamp. + NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override; + + // Only invoked when associated GetNodeReadiness() returned kReadyForProcess. + void FillInputSet(Timestamp input_timestamp, + InputStreamShardSet* input_set) override; +}; + +} // namespace mediapipe + +#endif // MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_EARLY_CLOSE_INPUT_STREAM_HANDLER_H_
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/fixed_size_input_stream_handler.cc b/third_party/mediapipe/src/mediapipe/framework/stream_handler/fixed_size_input_stream_handler.cc index fd51a7383..bc3dd938 100644 --- a/third_party/mediapipe/src/mediapipe/framework/stream_handler/fixed_size_input_stream_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/fixed_size_input_stream_handler.cc
@@ -1,4 +1,4 @@ -// Copyright 2019 The MediaPipe Authors. +// Copyright 2023 The MediaPipe Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,219 +11,186 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include "mediapipe/framework/stream_handler/fixed_size_input_stream_handler.h" +#include <algorithm> +#include <list> #include <memory> +#include <utility> #include <vector> +#include "absl/log/check.h" +#include "absl/log/log.h" +#include "absl/synchronization/mutex.h" +#include "mediapipe/framework/calculator_context_manager.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/collection_item_id.h" +#include "mediapipe/framework/input_stream_handler.h" +#include "mediapipe/framework/mediapipe_options.pb.h" +#include "mediapipe/framework/packet.h" #include "mediapipe/framework/stream_handler/default_input_stream_handler.h" -// TODO: Move protos in another CL after the C++ code migration. #include "mediapipe/framework/stream_handler/fixed_size_input_stream_handler.pb.h" +#include "mediapipe/framework/tool/tag_map.h" +#include "absl/log/absl_check.h" namespace mediapipe { -// Input stream handler that limits each input queue to a maximum of -// target_queue_size packets, discarding older packets as needed. When a -// timestamp is dropped from a stream, it is dropped from all others as well. -// -// For example, a calculator node with one input stream and the following input -// stream handler specs: -// -// node { -// calculator: "CalculatorRunningAtOneFps" -// input_stream: "packets_streaming_in_at_ten_fps" -// input_stream_handler { -// input_stream_handler: "FixedSizeInputStreamHandler" -// } -// } -// -// will always try to keep the newest packet in the input stream. -// -// A few details: FixedSizeInputStreamHandler takes action when any stream grows -// to trigger_queue_size or larger. It then keeps at most target_queue_size -// packets in every InputStreamImpl. Every stream is truncated at the same -// timestamp, so that each included timestamp delivers the same packets as -// DefaultInputStreamHandler includes. -// -class FixedSizeInputStreamHandler : public DefaultInputStreamHandler { - public: - FixedSizeInputStreamHandler() = delete; - FixedSizeInputStreamHandler(std::shared_ptr<tool::TagMap> tag_map, - CalculatorContextManager* cc_manager, - const MediaPipeOptions& options, - bool calculator_run_in_parallel) - : DefaultInputStreamHandler(std::move(tag_map), cc_manager, options, - calculator_run_in_parallel) { - const auto& ext = - options.GetExtension(FixedSizeInputStreamHandlerOptions::ext); - trigger_queue_size_ = ext.trigger_queue_size(); - target_queue_size_ = ext.target_queue_size(); - fixed_min_size_ = ext.fixed_min_size(); - pending_ = false; - kept_timestamp_ = Timestamp::Unset(); - // TODO: Either re-enable SetLatePreparation(true) with - // CalculatorContext::InputTimestamp set correctly, or remove the - // implementation of SetLatePreparation. - } +FixedSizeInputStreamHandler::FixedSizeInputStreamHandler( + std::shared_ptr<tool::TagMap> tag_map, CalculatorContextManager* cc_manager, + const mediapipe::MediaPipeOptions& options, bool calculator_run_in_parallel) + : DefaultInputStreamHandler(std::move(tag_map), cc_manager, options, + calculator_run_in_parallel) { + const auto& ext = + options.GetExtension(mediapipe::FixedSizeInputStreamHandlerOptions::ext); + trigger_queue_size_ = ext.trigger_queue_size(); + target_queue_size_ = ext.target_queue_size(); + fixed_min_size_ = ext.fixed_min_size(); + pending_ = false; + kept_timestamp_ = Timestamp::Unset(); + // TODO: Either re-enable SetLatePreparation(true) with + // CalculatorContext::InputTimestamp set correctly, or remove the + // implementation of SetLatePreparation. +} - private: - // Drops packets if all input streams exceed trigger_queue_size. - void EraseAllSurplus() ABSL_EXCLUSIVE_LOCKS_REQUIRED(erase_mutex_) { - Timestamp min_timestamp_all_streams = Timestamp::Max(); - for (const auto& stream : input_stream_managers_) { - // Check whether every InputStreamImpl grew beyond trigger_queue_size. - if (stream->QueueSize() < trigger_queue_size_) { - return; - } - Timestamp min_timestamp = - stream->GetMinTimestampAmongNLatest(target_queue_size_); - - // Record the min timestamp among the newest target_queue_size_ packets - // across all InputStreamImpls. - min_timestamp_all_streams = - std::min(min_timestamp_all_streams, min_timestamp); +void FixedSizeInputStreamHandler::EraseAllSurplus() { + Timestamp min_timestamp_all_streams = Timestamp::Max(); + for (const auto& stream : input_stream_managers_) { + // Check whether every InputStreamImpl grew beyond trigger_queue_size. + if (stream->QueueSize() < trigger_queue_size_) { + return; } - for (auto& stream : input_stream_managers_) { - stream->ErasePacketsEarlierThan(min_timestamp_all_streams); + Timestamp min_timestamp = + stream->GetMinTimestampAmongNLatest(target_queue_size_); + + // Record the min timestamp among the newest target_queue_size_ packets + // across all InputStreamImpls. + min_timestamp_all_streams = + std::min(min_timestamp_all_streams, min_timestamp); + } + for (auto& stream : input_stream_managers_) { + stream->ErasePacketsEarlierThan(min_timestamp_all_streams); + } +} + +Timestamp FixedSizeInputStreamHandler::PreviousAllowedInStream( + Timestamp bound) { + return bound.IsRangeValue() ? bound - 1 : bound; +} + +Timestamp FixedSizeInputStreamHandler::MinStreamBound() { + Timestamp min_bound = Timestamp::Done(); + for (const auto& stream : input_stream_managers_) { + Timestamp stream_bound = stream->GetMinTimestampAmongNLatest(1); + if (stream_bound > Timestamp::Unset()) { + stream_bound = stream_bound.NextAllowedInStream(); + } else { + stream_bound = stream->MinTimestampOrBound(nullptr); + } + min_bound = std::min(min_bound, stream_bound); + } + return min_bound; +} + +Timestamp FixedSizeInputStreamHandler::MinTimestampToProcess() { + Timestamp min_bound = Timestamp::Done(); + for (const auto& stream : input_stream_managers_) { + bool empty; + Timestamp stream_timestamp = stream->MinTimestampOrBound(&empty); + // If we're using the stream's *bound*, we only want to process up to the + // packet *before* the bound, because a packet may still arrive at that + // time. + if (empty) { + stream_timestamp = PreviousAllowedInStream(stream_timestamp); + } + min_bound = std::min(min_bound, stream_timestamp); + } + return min_bound; +} + +void FixedSizeInputStreamHandler::EraseAnySurplus(bool keep_one) { + // Record the most recent first kept timestamp on any stream. + for (const auto& stream : input_stream_managers_) { + int32_t queue_size = (stream->QueueSize() >= trigger_queue_size_) + ? target_queue_size_ + : trigger_queue_size_ - 1; + if (stream->QueueSize() > queue_size) { + kept_timestamp_ = std::max( + kept_timestamp_, stream->GetMinTimestampAmongNLatest(queue_size + 1) + .NextAllowedInStream()); } } - - // Returns the latest timestamp allowed before a bound. - Timestamp PreviousAllowedInStream(Timestamp bound) { - return bound.IsRangeValue() ? bound - 1 : bound; + if (keep_one) { + // In order to preserve one viable timestamp, do not truncate past + // the timestamp bound of the least current stream. + kept_timestamp_ = + std::min(kept_timestamp_, PreviousAllowedInStream(MinStreamBound())); } - - // Returns the lowest timestamp at which a packet may arrive at any stream. - Timestamp MinStreamBound() { - Timestamp min_bound = Timestamp::Done(); - for (const auto& stream : input_stream_managers_) { - Timestamp stream_bound = stream->GetMinTimestampAmongNLatest(1); - if (stream_bound > Timestamp::Unset()) { - stream_bound = stream_bound.NextAllowedInStream(); - } else { - stream_bound = stream->MinTimestampOrBound(nullptr); - } - min_bound = std::min(min_bound, stream_bound); - } - return min_bound; + for (auto& stream : input_stream_managers_) { + stream->ErasePacketsEarlierThan(kept_timestamp_); } +} - // Returns the lowest timestamp of a packet ready to process. - Timestamp MinTimestampToProcess() { - Timestamp min_bound = Timestamp::Done(); - for (const auto& stream : input_stream_managers_) { - bool empty; - Timestamp stream_timestamp = stream->MinTimestampOrBound(&empty); - // If we're using the stream's *bound*, we only want to process up to the - // packet *before* the bound, because a packet may still arrive at that - // time. - if (empty) { - stream_timestamp = PreviousAllowedInStream(stream_timestamp); - } - min_bound = std::min(min_bound, stream_timestamp); - } - return min_bound; +void FixedSizeInputStreamHandler::EraseSurplusPackets(bool keep_one) { + return (fixed_min_size_) ? EraseAllSurplus() : EraseAnySurplus(keep_one); +} + +NodeReadiness FixedSizeInputStreamHandler::GetNodeReadiness( + Timestamp* min_stream_timestamp) { + ABSL_DCHECK(min_stream_timestamp); + absl::MutexLock lock(&erase_mutex_); + // kReadyForProcess is returned only once until FillInputSet completes. + // In late_preparation mode, GetNodeReadiness must return kReadyForProcess + // exactly once for each input-set produced. Here, GetNodeReadiness + // releases just one input-set at a time and then disables input queue + // truncation until that promised input-set is consumed. + if (pending_) { + return NodeReadiness::kNotReady; } + EraseSurplusPackets(false); + NodeReadiness result = + DefaultInputStreamHandler::GetNodeReadiness(min_stream_timestamp); - // Keeps only the most recent target_queue_size packets in each stream - // exceeding trigger_queue_size. Also, discards all packets older than the - // first kept timestamp on any stream. - void EraseAnySurplus(bool keep_one) - ABSL_EXCLUSIVE_LOCKS_REQUIRED(erase_mutex_) { - // Record the most recent first kept timestamp on any stream. - for (const auto& stream : input_stream_managers_) { - int32_t queue_size = (stream->QueueSize() >= trigger_queue_size_) - ? target_queue_size_ - : trigger_queue_size_ - 1; - if (stream->QueueSize() > queue_size) { - kept_timestamp_ = std::max( - kept_timestamp_, stream->GetMinTimestampAmongNLatest(queue_size + 1) - .NextAllowedInStream()); - } - } - if (keep_one) { - // In order to preserve one viable timestamp, do not truncate past - // the timestamp bound of the least current stream. - kept_timestamp_ = - std::min(kept_timestamp_, PreviousAllowedInStream(MinStreamBound())); - } - for (auto& stream : input_stream_managers_) { - stream->ErasePacketsEarlierThan(kept_timestamp_); - } - } - - void EraseSurplusPackets(bool keep_one) - ABSL_EXCLUSIVE_LOCKS_REQUIRED(erase_mutex_) { - return (fixed_min_size_) ? EraseAllSurplus() : EraseAnySurplus(keep_one); - } - - NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override { - DCHECK(min_stream_timestamp); - absl::MutexLock lock(&erase_mutex_); - // kReadyForProcess is returned only once until FillInputSet completes. - // In late_preparation mode, GetNodeReadiness must return kReadyForProcess - // exactly once for each input-set produced. Here, GetNodeReadiness - // releases just one input-set at a time and then disables input queue - // truncation until that promised input-set is consumed. - if (pending_) { - return NodeReadiness::kNotReady; - } + // If a packet has arrived below kept_timestamp_, recalculate. + while (*min_stream_timestamp < kept_timestamp_ && + result == NodeReadiness::kReadyForProcess) { EraseSurplusPackets(false); - NodeReadiness result = - DefaultInputStreamHandler::GetNodeReadiness(min_stream_timestamp); - - // If a packet has arrived below kept_timestamp_, recalculate. - while (*min_stream_timestamp < kept_timestamp_ && - result == NodeReadiness::kReadyForProcess) { - EraseSurplusPackets(false); - result = - DefaultInputStreamHandler::GetNodeReadiness(min_stream_timestamp); - } - pending_ = (result == NodeReadiness::kReadyForProcess); - return result; + result = DefaultInputStreamHandler::GetNodeReadiness(min_stream_timestamp); } + pending_ = (result == NodeReadiness::kReadyForProcess); + return result; +} - void AddPackets(CollectionItemId id, - const std::list<Packet>& packets) override { - InputStreamHandler::AddPackets(id, packets); - absl::MutexLock lock(&erase_mutex_); - if (!pending_) { - EraseSurplusPackets(false); - } +void FixedSizeInputStreamHandler::AddPackets(CollectionItemId id, + const std::list<Packet>& packets) { + InputStreamHandler::AddPackets(id, packets); + absl::MutexLock lock(&erase_mutex_); + if (!pending_) { + EraseSurplusPackets(false); } +} - void MovePackets(CollectionItemId id, std::list<Packet>* packets) override { - InputStreamHandler::MovePackets(id, packets); - absl::MutexLock lock(&erase_mutex_); - if (!pending_) { - EraseSurplusPackets(false); - } +void FixedSizeInputStreamHandler::MovePackets(CollectionItemId id, + std::list<Packet>* packets) { + InputStreamHandler::MovePackets(id, packets); + absl::MutexLock lock(&erase_mutex_); + if (!pending_) { + EraseSurplusPackets(false); } +} - void FillInputSet(Timestamp input_timestamp, - InputStreamShardSet* input_set) override { - CHECK(input_set); - absl::MutexLock lock(&erase_mutex_); - if (!pending_) { - LOG(ERROR) << "FillInputSet called without GetNodeReadiness."; - } - // input_timestamp is recalculated here to process the most recent packets. - EraseSurplusPackets(true); - input_timestamp = MinTimestampToProcess(); - DefaultInputStreamHandler::FillInputSet(input_timestamp, input_set); - pending_ = false; +void FixedSizeInputStreamHandler::FillInputSet(Timestamp input_timestamp, + InputStreamShardSet* input_set) { + ABSL_CHECK(input_set); + absl::MutexLock lock(&erase_mutex_); + if (!pending_) { + LOG(ERROR) << "FillInputSet called without GetNodeReadiness."; } - - private: - int32_t trigger_queue_size_; - int32_t target_queue_size_; - bool fixed_min_size_; - // Indicates that GetNodeReadiness has returned kReadyForProcess once, and - // the corresponding call to FillInputSet has not yet completed. - bool pending_ ABSL_GUARDED_BY(erase_mutex_); - // The timestamp used to truncate all input streams. - Timestamp kept_timestamp_ ABSL_GUARDED_BY(erase_mutex_); - absl::Mutex erase_mutex_; -}; + // input_timestamp is recalculated here to process the most recent packets. + EraseSurplusPackets(true); + input_timestamp = MinTimestampToProcess(); + DefaultInputStreamHandler::FillInputSet(input_timestamp, input_set); + pending_ = false; +} REGISTER_INPUT_STREAM_HANDLER(FixedSizeInputStreamHandler);
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/fixed_size_input_stream_handler.h b/third_party/mediapipe/src/mediapipe/framework/stream_handler/fixed_size_input_stream_handler.h new file mode 100644 index 0000000..a00bdda --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/fixed_size_input_stream_handler.h
@@ -0,0 +1,108 @@ +// Copyright 2023 The MediaPipe Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_FIXED_SIZE_INPUT_STREAM_HANDLER_H_ +#define MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_FIXED_SIZE_INPUT_STREAM_HANDLER_H_ + +#include <cstdint> +#include <list> +#include <memory> + +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "mediapipe/framework/calculator_context_manager.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/collection_item_id.h" +#include "mediapipe/framework/input_stream_handler.h" +#include "mediapipe/framework/stream_handler/default_input_stream_handler.h" + +namespace mediapipe { + +// Input stream handler that limits each input queue to a maximum of +// target_queue_size packets, discarding older packets as needed. When a +// timestamp is dropped from a stream, it is dropped from all others as well. +// +// For example, a calculator node with one input stream and the following input +// stream handler specs: +// +// node { +// calculator: "CalculatorRunningAtOneFps" +// input_stream: "packets_streaming_in_at_ten_fps" +// input_stream_handler { +// input_stream_handler: "FixedSizeInputStreamHandler" +// } +// } +// +// will always try to keep the newest packet in the input stream. +// +// A few details: FixedSizeInputStreamHandler takes action when any stream grows +// to trigger_queue_size or larger. It then keeps at most target_queue_size +// packets in every InputStreamImpl. Every stream is truncated at the same +// timestamp, so that each included timestamp delivers the same packets as +// DefaultInputStreamHandler includes. +class FixedSizeInputStreamHandler : public DefaultInputStreamHandler { + public: + FixedSizeInputStreamHandler() = delete; + FixedSizeInputStreamHandler(std::shared_ptr<tool::TagMap> tag_map, + CalculatorContextManager* cc_manager, + const MediaPipeOptions& options, + bool calculator_run_in_parallel); + + private: + // Drops packets if all input streams exceed trigger_queue_size. + void EraseAllSurplus() ABSL_EXCLUSIVE_LOCKS_REQUIRED(erase_mutex_); + + // Returns the latest timestamp allowed before a bound. + Timestamp PreviousAllowedInStream(Timestamp bound); + + // Returns the lowest timestamp at which a packet may arrive at any stream. + Timestamp MinStreamBound(); + + // Returns the lowest timestamp of a packet ready to process. + Timestamp MinTimestampToProcess(); + + // Keeps only the most recent target_queue_size packets in each stream + // exceeding trigger_queue_size. Also, discards all packets older than the + // first kept timestamp on any stream. + void EraseAnySurplus(bool keep_one) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(erase_mutex_); + + void EraseSurplusPackets(bool keep_one) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(erase_mutex_); + + NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override; + + void AddPackets(CollectionItemId id, + const std::list<Packet>& packets) override; + + void MovePackets(CollectionItemId id, std::list<Packet>* packets) override; + + void FillInputSet(Timestamp input_timestamp, + InputStreamShardSet* input_set) override; + + private: + int32_t trigger_queue_size_; + int32_t target_queue_size_; + bool fixed_min_size_; + // Indicates that GetNodeReadiness has returned kReadyForProcess once, and + // the corresponding call to FillInputSet has not yet completed. + bool pending_ ABSL_GUARDED_BY(erase_mutex_); + // The timestamp used to truncate all input streams. + Timestamp kept_timestamp_ ABSL_GUARDED_BY(erase_mutex_); + absl::Mutex erase_mutex_; +}; + +} // namespace mediapipe + +#endif // MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_FIXED_SIZE_INPUT_STREAM_HANDLER_H_
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/immediate_input_stream_handler.cc b/third_party/mediapipe/src/mediapipe/framework/stream_handler/immediate_input_stream_handler.cc index 2951c833..b4920745 100644 --- a/third_party/mediapipe/src/mediapipe/framework/stream_handler/immediate_input_stream_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/immediate_input_stream_handler.cc
@@ -11,66 +11,34 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include "mediapipe/framework/stream_handler/immediate_input_stream_handler.h" +#include <algorithm> +#include <functional> #include <memory> #include <vector> -#include "absl/log/absl_check.h" +#include "absl/log/check.h" +#include "absl/status/status.h" +#include "absl/synchronization/mutex.h" +#include "mediapipe/framework/calculator_context_manager.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/input_stream_handler.h" +#include "mediapipe/framework/mediapipe_options.pb.h" +#include "mediapipe/framework/tool/tag_map.h" +#include "absl/log/absl_check.h" namespace mediapipe { using SyncSet = InputStreamHandler::SyncSet; -// An input stream handler that delivers input packets to the Calculator -// immediately, with no dependency between input streams. It also invokes -// Calculator::Process when any input stream becomes done. -// -// NOTE: If packets arrive successively on different input streams with -// identical or decreasing timestamps, this input stream handler will -// invoke its Calculator with a sequence of InputTimestamps that is -// non-increasing. Its Calculator is responsible for accumulating packets -// with the required timetamps before processing and delivering output. -// -class ImmediateInputStreamHandler : public InputStreamHandler { - public: - ImmediateInputStreamHandler() = delete; - ImmediateInputStreamHandler( - std::shared_ptr<tool::TagMap> tag_map, - CalculatorContextManager* calculator_context_manager, - const MediaPipeOptions& options, bool calculator_run_in_parallel); - - protected: - // Reinitializes this InputStreamHandler before each CalculatorGraph run. - void PrepareForRun(std::function<void()> headers_ready_callback, - std::function<void()> notification_callback, - std::function<void(CalculatorContext*)> schedule_callback, - std::function<void(absl::Status)> error_callback) override; - - // Returns kReadyForProcess whenever a Packet is available at any of - // the input streams, or any input stream becomes done. - NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override; - - // Selects a packet on each stream with an available packet with the - // specified timestamp, leaving other input streams unaffected. - void FillInputSet(Timestamp input_timestamp, - InputStreamShardSet* input_set) override; - - // Returns the number of sync-sets maintained by this input-handler. - int SyncSetCount() override; - - absl::Mutex mutex_; - // The packet-set builder for each input stream. - std::vector<SyncSet> sync_sets_ ABSL_GUARDED_BY(mutex_); - // The input timestamp for each kReadyForProcess input stream. - std::vector<Timestamp> ready_timestamps_ ABSL_GUARDED_BY(mutex_); -}; REGISTER_INPUT_STREAM_HANDLER(ImmediateInputStreamHandler); ImmediateInputStreamHandler::ImmediateInputStreamHandler( std::shared_ptr<tool::TagMap> tag_map, CalculatorContextManager* calculator_context_manager, - const MediaPipeOptions& options, bool calculator_run_in_parallel) + const mediapipe::MediaPipeOptions& options, bool calculator_run_in_parallel) : InputStreamHandler(tag_map, calculator_context_manager, options, calculator_run_in_parallel) { for (auto id = tag_map->BeginId(); id < tag_map->EndId(); ++id) {
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/immediate_input_stream_handler.h b/third_party/mediapipe/src/mediapipe/framework/stream_handler/immediate_input_stream_handler.h new file mode 100644 index 0000000..dd15ad99 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/immediate_input_stream_handler.h
@@ -0,0 +1,77 @@ +// Copyright 2023 The MediaPipe Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_IMMEDIATE_INPUT_STREAM_HANDLER_H_ +#define MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_IMMEDIATE_INPUT_STREAM_HANDLER_H_ + +#include <functional> +#include <memory> +#include <vector> + +#include "absl/base/thread_annotations.h" +#include "absl/status/status.h" +#include "absl/synchronization/mutex.h" +#include "mediapipe/framework/calculator_context_manager.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/input_stream_handler.h" +#include "mediapipe/framework/tool/tag_map.h" + +namespace mediapipe { + +// An input stream handler that delivers input packets to the Calculator +// immediately, with no dependency between input streams. It also invokes +// Calculator::Process when any input stream becomes done. +// +// NOTE: If packets arrive successively on different input streams with +// identical or decreasing timestamps, this input stream handler will +// invoke its Calculator with a sequence of InputTimestamps that is +// non-increasing. Its Calculator is responsible for accumulating packets +// with the required timestamps before processing and delivering output. +class ImmediateInputStreamHandler : public InputStreamHandler { + public: + ImmediateInputStreamHandler() = delete; + ImmediateInputStreamHandler( + std::shared_ptr<tool::TagMap> tag_map, + CalculatorContextManager* calculator_context_manager, + const MediaPipeOptions& options, bool calculator_run_in_parallel); + + protected: + // Reinitializes this InputStreamHandler before each CalculatorGraph run. + void PrepareForRun(std::function<void()> headers_ready_callback, + std::function<void()> notification_callback, + std::function<void(CalculatorContext*)> schedule_callback, + std::function<void(absl::Status)> error_callback) override; + + // Returns kReadyForProcess whenever a Packet is available at any of + // the input streams, or any input stream becomes done. + NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override; + + // Selects a packet on each stream with an available packet with the + // specified timestamp, leaving other input streams unaffected. + void FillInputSet(Timestamp input_timestamp, + InputStreamShardSet* input_set) override; + + // Returns the number of sync-sets maintained by this input-handler. + int SyncSetCount() override; + + absl::Mutex mutex_; + // The packet-set builder for each input stream. + std::vector<SyncSet> sync_sets_ ABSL_GUARDED_BY(mutex_); + // The input timestamp for each kReadyForProcess input stream. + std::vector<Timestamp> ready_timestamps_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace mediapipe + +#endif // MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_IMMEDIATE_INPUT_STREAM_HANDLER_H_
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/in_order_output_stream_handler.cc b/third_party/mediapipe/src/mediapipe/framework/stream_handler/in_order_output_stream_handler.cc index 8faaace..7fbee65 100644 --- a/third_party/mediapipe/src/mediapipe/framework/stream_handler/in_order_output_stream_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/in_order_output_stream_handler.cc
@@ -14,10 +14,10 @@ #include "mediapipe/framework/stream_handler/in_order_output_stream_handler.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/collection.h" #include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/output_stream_shard.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/mux_input_stream_handler.cc b/third_party/mediapipe/src/mediapipe/framework/stream_handler/mux_input_stream_handler.cc index 699aa91..7d686aa 100644 --- a/third_party/mediapipe/src/mediapipe/framework/stream_handler/mux_input_stream_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/mux_input_stream_handler.cc
@@ -11,152 +11,125 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include "mediapipe/framework/stream_handler/mux_input_stream_handler.h" -#include "absl/log/absl_check.h" +#include <utility> + +#include "absl/log/check.h" #include "absl/strings/substitute.h" #include "absl/synchronization/mutex.h" +#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/input_stream_handler.h" -#include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { -// Implementation of the input stream handler for the MuxCalculator. -// -// One of the input streams is the control stream; all the other input streams -// are data streams. To make MuxInputStreamHandler work properly, the tag of the -// input streams must obey the following rules: -// Let N be the number of input streams. Data streams must use tag "INPUT" with -// index 0, ..., N - 2; the control stream must use tag "SELECT". -// -// The control stream carries packets of type 'int'. The 'int' value in a -// control stream packet must be a valid index in the range 0, ..., N - 2 and -// select the data stream at that index. The selected data stream must have a -// packet with the same timestamp as the control stream packet. -// -// When the control stream is done, GetNodeReadiness() returns -// NodeReadiness::kReadyForClose. -// -// TODO: pass the input stream tags to the MuxInputStreamHandler -// constructor so that it can refer to input streams by tag. See b/30125118. -class MuxInputStreamHandler : public InputStreamHandler { - public: - MuxInputStreamHandler() = delete; - MuxInputStreamHandler(std::shared_ptr<tool::TagMap> tag_map, - CalculatorContextManager* cc_manager, - const MediaPipeOptions& options, - bool calculator_run_in_parallel) - : InputStreamHandler(std::move(tag_map), cc_manager, options, - calculator_run_in_parallel) {} +CollectionItemId MuxInputStreamHandler::GetControlStreamId() const { + return input_stream_managers_.EndId() - 1; +} +void MuxInputStreamHandler::RemoveOutdatedDataPackets(Timestamp timestamp) { + const CollectionItemId control_stream_id = GetControlStreamId(); + for (CollectionItemId id = input_stream_managers_.BeginId(); + id < control_stream_id; ++id) { + input_stream_managers_.Get(id)->ErasePacketsEarlierThan(timestamp); + } +} - protected: - // In MuxInputStreamHandler, a node is "ready" if: - // - the control stream is done (need to call Close() in this case), or - // - we have received the packets on the control stream and the selected data - // stream at the next timestamp. - NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override { - DCHECK(min_stream_timestamp); - absl::MutexLock lock(&input_streams_mutex_); +// In MuxInputStreamHandler, a node is "ready" if: +// - the control stream is done (need to call Close() in this case), or +// - we have received the packets on the control stream and the selected data +// stream at the next timestamp. +NodeReadiness MuxInputStreamHandler::GetNodeReadiness( + Timestamp* min_stream_timestamp) { + ABSL_DCHECK(min_stream_timestamp); + absl::MutexLock lock(&input_streams_mutex_); - const auto& control_stream = - input_stream_managers_.Get(input_stream_managers_.EndId() - 1); - bool empty; - *min_stream_timestamp = control_stream->MinTimestampOrBound(&empty); - if (empty) { - if (*min_stream_timestamp == Timestamp::Done()) { - // Calculator is done if the control input stream is done. - return NodeReadiness::kReadyForClose; - } - // Calculator is not ready to run if the control input stream is empty. + const auto& control_stream = input_stream_managers_.Get(GetControlStreamId()); + bool empty; + *min_stream_timestamp = control_stream->MinTimestampOrBound(&empty); + + // Data streams may contain some outdated packets which failed to be popped + // out during "FillInputSet". (This handler doesn't sync input streams, + // hence "FillInputSet" can be triggered before every input stream is + // filled with packets corresponding to the same timestamp.) + RemoveOutdatedDataPackets(*min_stream_timestamp); + if (empty) { + if (*min_stream_timestamp == Timestamp::Done()) { + // Calculator is done if the control input stream is done. + return NodeReadiness::kReadyForClose; + } + // Calculator is not ready to run if the control input stream is empty. + return NodeReadiness::kNotReady; + } + + Packet control_packet = control_stream->QueueHead(); + ABSL_CHECK(!control_packet.IsEmpty()); + int control_value = control_packet.Get<int>(); + ABSL_CHECK_LE(0, control_value); + ABSL_CHECK_LT(control_value, input_stream_managers_.NumEntries() - 1); + const auto& data_stream = input_stream_managers_.Get( + input_stream_managers_.BeginId() + control_value); + + Timestamp stream_timestamp = data_stream->MinTimestampOrBound(&empty); + if (empty) { + if (stream_timestamp <= *min_stream_timestamp) { + // "data_stream" didn't receive a packet corresponding to the current + // "control_stream" packet yet. return NodeReadiness::kNotReady; } - - Packet control_packet = control_stream->QueueHead(); - CHECK(!control_packet.IsEmpty()); - int control_value = control_packet.Get<int>(); - ABSL_CHECK_LE(0, control_value); - ABSL_CHECK_LT(control_value, input_stream_managers_.NumEntries() - 1); - const auto& data_stream = input_stream_managers_.Get( - input_stream_managers_.BeginId() + control_value); - - // Data stream may contain some outdated packets which failed to be popped - // out during "FillInputSet". (This handler doesn't sync input streams, - // hence "FillInputSet" can be triggerred before every input stream is - // filled with packets corresponding to the same timestamp.) - data_stream->ErasePacketsEarlierThan(*min_stream_timestamp); - Timestamp stream_timestamp = data_stream->MinTimestampOrBound(&empty); - if (empty) { - if (stream_timestamp <= *min_stream_timestamp) { - // "data_stream" didn't receive a packet corresponding to the current - // "control_stream" packet yet. - return NodeReadiness::kNotReady; - } - // "data_stream" timestamp bound update detected. - return NodeReadiness::kReadyForProcess; - } - if (stream_timestamp > *min_stream_timestamp) { - // The earliest packet "data_stream" holds corresponds to a control packet - // yet to arrive, which means there won't be a "data_stream" packet - // corresponding to the current "control_stream" packet, which should be - // indicated as timestamp boun update. - return NodeReadiness::kReadyForProcess; - } - ABSL_CHECK_EQ(stream_timestamp, *min_stream_timestamp); + // "data_stream" timestamp bound update detected. return NodeReadiness::kReadyForProcess; } - - // Only invoked when associated GetNodeReadiness() returned kReadyForProcess. - void FillInputSet(Timestamp input_timestamp, - InputStreamShardSet* input_set) override { - CHECK(input_timestamp.IsAllowedInStream()); - CHECK(input_set); - absl::MutexLock lock(&input_streams_mutex_); - - const CollectionItemId control_stream_id = - input_stream_managers_.EndId() - 1; - auto& control_stream = input_stream_managers_.Get(control_stream_id); - int num_packets_dropped = 0; - bool stream_is_done = false; - Packet control_packet = control_stream->PopPacketAtTimestamp( - input_timestamp, &num_packets_dropped, &stream_is_done); - ABSL_CHECK_EQ(num_packets_dropped, 0) - << absl::Substitute("Dropped $0 packet(s) on input stream \"$1\".", - num_packets_dropped, control_stream->Name()); - CHECK(!control_packet.IsEmpty()); - int control_value = control_packet.Get<int>(); - AddPacketToShard(&input_set->Get(control_stream_id), - std::move(control_packet), stream_is_done); - - const CollectionItemId data_stream_id = - input_stream_managers_.BeginId() + control_value; - ABSL_CHECK_LE(input_stream_managers_.BeginId(), data_stream_id); - ABSL_CHECK_LT(data_stream_id, control_stream_id); - auto& data_stream = input_stream_managers_.Get(data_stream_id); - stream_is_done = false; - Packet data_packet = data_stream->PopPacketAtTimestamp( - input_timestamp, &num_packets_dropped, &stream_is_done); - ABSL_CHECK_EQ(num_packets_dropped, 0) - << absl::Substitute("Dropped $0 packet(s) on input stream \"$1\".", - num_packets_dropped, data_stream->Name()); - AddPacketToShard(&input_set->Get(data_stream_id), std::move(data_packet), - stream_is_done); - - // Discard old packets on other streams. - // Note that control_stream_id is the last valid id. - auto next_timestamp = input_timestamp.NextAllowedInStream(); - for (CollectionItemId id = input_stream_managers_.BeginId(); - id < control_stream_id; ++id) { - if (id == data_stream_id) continue; - auto& other_stream = input_stream_managers_.Get(id); - other_stream->ErasePacketsEarlierThan(next_timestamp); - } + if (stream_timestamp > *min_stream_timestamp) { + // The earliest packet "data_stream" holds corresponds to a control packet + // yet to arrive, which means there won't be a "data_stream" packet + // corresponding to the current "control_stream" packet, which should be + // indicated as timestamp boun update. + return NodeReadiness::kReadyForProcess; } + ABSL_CHECK_EQ(stream_timestamp, *min_stream_timestamp); + return NodeReadiness::kReadyForProcess; +} - private: - // Must be acquired when manipulating the control and data streams to ensure - // we have a consistent view of the two streams. - absl::Mutex input_streams_mutex_; -}; +// Only invoked when associated GetNodeReadiness() returned kReadyForProcess. +void MuxInputStreamHandler::FillInputSet(Timestamp input_timestamp, + InputStreamShardSet* input_set) { + ABSL_CHECK(input_timestamp.IsAllowedInStream()); + ABSL_CHECK(input_set); + absl::MutexLock lock(&input_streams_mutex_); + + const CollectionItemId control_stream_id = GetControlStreamId(); + auto& control_stream = input_stream_managers_.Get(control_stream_id); + int num_packets_dropped = 0; + bool stream_is_done = false; + Packet control_packet = control_stream->PopPacketAtTimestamp( + input_timestamp, &num_packets_dropped, &stream_is_done); + ABSL_CHECK_EQ(num_packets_dropped, 0) + << absl::Substitute("Dropped $0 packet(s) on input stream \"$1\".", + num_packets_dropped, control_stream->Name()); + ABSL_CHECK(!control_packet.IsEmpty()); + int control_value = control_packet.Get<int>(); + AddPacketToShard(&input_set->Get(control_stream_id), + std::move(control_packet), stream_is_done); + + const CollectionItemId data_stream_id = + input_stream_managers_.BeginId() + control_value; + ABSL_CHECK_LE(input_stream_managers_.BeginId(), data_stream_id); + ABSL_CHECK_LT(data_stream_id, control_stream_id); + auto& data_stream = input_stream_managers_.Get(data_stream_id); + stream_is_done = false; + Packet data_packet = data_stream->PopPacketAtTimestamp( + input_timestamp, &num_packets_dropped, &stream_is_done); + ABSL_CHECK_EQ(num_packets_dropped, 0) + << absl::Substitute("Dropped $0 packet(s) on input stream \"$1\".", + num_packets_dropped, data_stream->Name()); + AddPacketToShard(&input_set->Get(data_stream_id), std::move(data_packet), + stream_is_done); + + // Discard old packets on data streams. + RemoveOutdatedDataPackets(input_timestamp.NextAllowedInStream()); +} REGISTER_INPUT_STREAM_HANDLER(MuxInputStreamHandler);
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/mux_input_stream_handler.h b/third_party/mediapipe/src/mediapipe/framework/stream_handler/mux_input_stream_handler.h new file mode 100644 index 0000000..63fdde0e6 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/mux_input_stream_handler.h
@@ -0,0 +1,80 @@ +// Copyright 2023 The MediaPipe Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_MUX_INPUT_STREAM_HANDLER_H_ +#define MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_MUX_INPUT_STREAM_HANDLER_H_ + +#include <memory> +#include <utility> + +#include "absl/synchronization/mutex.h" +#include "mediapipe/framework/calculator_context_manager.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/collection_item_id.h" +#include "mediapipe/framework/input_stream_handler.h" + +namespace mediapipe { + +// Implementation of the input stream handler for the MuxCalculator. +// +// One of the input streams is the control stream; all the other input streams +// are data streams. To make MuxInputStreamHandler work properly, the tag of the +// input streams must obey the following rules: +// Let N be the number of input streams. Data streams must use tag "INPUT" with +// index 0, ..., N - 2; the control stream must use tag "SELECT". +// +// The control stream carries packets of type 'int'. The 'int' value in a +// control stream packet must be a valid index in the range 0, ..., N - 2 and +// select the data stream at that index. The selected data stream must have a +// packet with the same timestamp as the control stream packet. +// +// When the control stream is done, GetNodeReadiness() returns +// NodeReadiness::kReadyForClose. +// +// TODO: pass the input stream tags to the MuxInputStreamHandler +// constructor so that it can refer to input streams by tag. See b/30125118. +class MuxInputStreamHandler : public InputStreamHandler { + public: + MuxInputStreamHandler() = delete; + MuxInputStreamHandler(std::shared_ptr<tool::TagMap> tag_map, + CalculatorContextManager* cc_manager, + const MediaPipeOptions& options, + bool calculator_run_in_parallel) + : InputStreamHandler(std::move(tag_map), cc_manager, options, + calculator_run_in_parallel) {} + + private: + CollectionItemId GetControlStreamId() const; + void RemoveOutdatedDataPackets(Timestamp timestamp); + + protected: + // In MuxInputStreamHandler, a node is "ready" if: + // - the control stream is done (need to call Close() in this case), or + // - we have received the packets on the control stream and the selected data + // stream at the next timestamp. + NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override; + + // Only invoked when associated GetNodeReadiness() returned kReadyForProcess. + void FillInputSet(Timestamp input_timestamp, + InputStreamShardSet* input_set) override; + + private: + // Must be acquired when manipulating the control and data streams to ensure + // we have a consistent view of the two streams. + absl::Mutex input_streams_mutex_; +}; + +} // namespace mediapipe + +#endif // MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_MUX_INPUT_STREAM_HANDLER_H_
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/sync_set_input_stream_handler.cc b/third_party/mediapipe/src/mediapipe/framework/stream_handler/sync_set_input_stream_handler.cc index adb4c05..f82ae6f4 100644 --- a/third_party/mediapipe/src/mediapipe/framework/stream_handler/sync_set_input_stream_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/sync_set_input_stream_handler.cc
@@ -11,92 +11,37 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include "mediapipe/framework/stream_handler/sync_set_input_stream_handler.h" -#include <algorithm> +#include <functional> +#include <set> +#include <string> +#include <utility> +#include <vector> -// TODO: Move protos in another CL after the C++ code migration. -#include "absl/log/absl_check.h" -#include "absl/strings/substitute.h" +#include "absl/log/check.h" #include "absl/synchronization/mutex.h" +#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/input_stream_handler.h" -#include "mediapipe/framework/mediapipe_options.pb.h" #include "mediapipe/framework/packet_set.h" +#include "mediapipe/framework/port/map_util.h" +#include "mediapipe/framework/port/status.h" #include "mediapipe/framework/stream_handler/sync_set_input_stream_handler.pb.h" #include "mediapipe/framework/timestamp.h" -#include "mediapipe/framework/tool/tag_map.h" +#include "absl/log/absl_check.h" namespace mediapipe { -// An input stream handler which separates the inputs into sets which -// are each independently synchronized. For example, if 5 inputs are -// present, then the first three can be grouped (and will be synchronized -// as if they were in a calculator with only those three streams) and the -// remaining 2 streams can be independently grouped. The calculator will -// always be called with all the available packets from a single sync set -// (never more than one). The input timestamps seen by the calculator -// will be ordered sequentially for each sync set but may jump around -// between sync sets. -class SyncSetInputStreamHandler : public InputStreamHandler { - public: - SyncSetInputStreamHandler() = delete; - SyncSetInputStreamHandler(std::shared_ptr<tool::TagMap> tag_map, - CalculatorContextManager* cc_manager, - const MediaPipeOptions& extendable_options, - bool calculator_run_in_parallel); - - void PrepareForRun(std::function<void()> headers_ready_callback, - std::function<void()> notification_callback, - std::function<void(CalculatorContext*)> schedule_callback, - std::function<void(absl::Status)> error_callback) override; - - protected: - // In SyncSetInputStreamHandler, a node is "ready" if any - // of its sync sets are ready in the traditional sense (See - // DefaultInputStreamHandler). - NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override; - - // Only invoked when associated GetNodeReadiness() returned kReadyForProcess. - // Populates packets for the ready sync-set, and populates timestamp bounds - // for all sync-sets. - void FillInputSet(Timestamp input_timestamp, - InputStreamShardSet* input_set) override; - - // Populates timestamp bounds for streams outside the ready sync-set. - void FillInputBounds(Timestamp input_timestamp, - InputStreamShardSet* input_set) - ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); - - // Returns the number of sync-sets maintained by this input-handler. - int SyncSetCount() override; - - private: - absl::Mutex mutex_; - // The ids of each set of inputs. - std::vector<SyncSet> sync_sets_ ABSL_GUARDED_BY(mutex_); - // The index of the ready sync set. A value of -1 indicates that no - // sync sets are ready. - int ready_sync_set_index_ ABSL_GUARDED_BY(mutex_) = -1; - // The timestamp at which the sync set is ready. If no sync set is - // ready then this variable should be Timestamp::Done() . - Timestamp ready_timestamp_ ABSL_GUARDED_BY(mutex_); -}; - REGISTER_INPUT_STREAM_HANDLER(SyncSetInputStreamHandler); -SyncSetInputStreamHandler::SyncSetInputStreamHandler( - std::shared_ptr<tool::TagMap> tag_map, CalculatorContextManager* cc_manager, - const MediaPipeOptions& extendable_options, bool calculator_run_in_parallel) - : InputStreamHandler(std::move(tag_map), cc_manager, extendable_options, - calculator_run_in_parallel) {} - void SyncSetInputStreamHandler::PrepareForRun( std::function<void()> headers_ready_callback, std::function<void()> notification_callback, std::function<void(CalculatorContext*)> schedule_callback, std::function<void(absl::Status)> error_callback) { const auto& handler_options = - options_.GetExtension(SyncSetInputStreamHandlerOptions::ext); + options_.GetExtension(mediapipe::SyncSetInputStreamHandlerOptions::ext); { absl::MutexLock lock(&mutex_); sync_sets_.clear(); @@ -109,8 +54,8 @@ int index; MEDIAPIPE_CHECK_OK(tool::ParseTagIndex(tag_index, &tag, &index)); CollectionItemId id = input_stream_managers_.GetId(tag, index); - CHECK(id.IsValid()) << "stream \"" << tag_index << "\" is not found."; - CHECK(!mediapipe::ContainsKey(used_ids, id)) + ABSL_CHECK(id.IsValid()) << "stream \"" << tag_index << "\" is not found."; + ABSL_CHECK(!mediapipe::ContainsKey(used_ids, id)) << "stream \"" << tag_index << "\" is in more than one sync set."; used_ids.insert(id); stream_ids.push_back(id); @@ -138,7 +83,7 @@ NodeReadiness SyncSetInputStreamHandler::GetNodeReadiness( Timestamp* min_stream_timestamp) { - DCHECK(min_stream_timestamp); + ABSL_DCHECK(min_stream_timestamp); absl::MutexLock lock(&mutex_); if (ready_sync_set_index_ >= 0) { *min_stream_timestamp = ready_timestamp_;
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/sync_set_input_stream_handler.h b/third_party/mediapipe/src/mediapipe/framework/stream_handler/sync_set_input_stream_handler.h new file mode 100644 index 0000000..67f1e49 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/sync_set_input_stream_handler.h
@@ -0,0 +1,97 @@ +// Copyright 2023 The MediaPipe Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_SYNC_SET_INPUT_STREAM_HANDLER_H_ +#define MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_SYNC_SET_INPUT_STREAM_HANDLER_H_ + +#include <functional> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/base/thread_annotations.h" +#include "absl/status/status.h" +#include "absl/synchronization/mutex.h" +#include "mediapipe/framework/calculator_context_manager.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/collection_item_id.h" +#include "mediapipe/framework/input_stream_handler.h" +#include "mediapipe/framework/mediapipe_options.pb.h" +#include "mediapipe/framework/packet_set.h" +#include "mediapipe/framework/stream_handler/sync_set_input_stream_handler.pb.h" +#include "mediapipe/framework/timestamp.h" +#include "mediapipe/framework/tool/tag_map.h" + +namespace mediapipe { + +// An input stream handler which separates the inputs into sets which +// are each independently synchronized. For example, if 5 inputs are +// present, then the first three can be grouped (and will be synchronized +// as if they were in a calculator with only those three streams) and the +// remaining 2 streams can be independently grouped. The calculator will +// always be called with all the available packets from a single sync set +// (never more than one). The input timestamps seen by the calculator +// will be ordered sequentially for each sync set but may jump around +// between sync sets. +class SyncSetInputStreamHandler : public InputStreamHandler { + public: + SyncSetInputStreamHandler() = delete; + SyncSetInputStreamHandler( + std::shared_ptr<tool::TagMap> tag_map, + CalculatorContextManager* cc_manager, + const mediapipe::MediaPipeOptions& extendable_options, + bool calculator_run_in_parallel) + : InputStreamHandler(std::move(tag_map), cc_manager, extendable_options, + calculator_run_in_parallel) {} + + void PrepareForRun(std::function<void()> headers_ready_callback, + std::function<void()> notification_callback, + std::function<void(CalculatorContext*)> schedule_callback, + std::function<void(absl::Status)> error_callback) override; + + protected: + // In SyncSetInputStreamHandler, a node is "ready" if any + // of its sync sets are ready in the traditional sense (See + // DefaultInputStreamHandler). + NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override; + + // Only invoked when associated GetNodeReadiness() returned kReadyForProcess. + // Populates packets for the ready sync-set, and populates timestamp bounds + // for all sync-sets. + void FillInputSet(Timestamp input_timestamp, + InputStreamShardSet* input_set) override; + + // Populates timestamp bounds for streams outside the ready sync-set. + void FillInputBounds(Timestamp input_timestamp, + InputStreamShardSet* input_set) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Returns the number of sync-sets maintained by this input-handler. + int SyncSetCount() override; + + private: + absl::Mutex mutex_; + // The ids of each set of inputs. + std::vector<SyncSet> sync_sets_ ABSL_GUARDED_BY(mutex_); + // The index of the ready sync set. A value of -1 indicates that no + // sync sets are ready. + int ready_sync_set_index_ ABSL_GUARDED_BY(mutex_) = -1; + // The timestamp at which the sync set is ready. If no sync set is + // ready then this variable should be Timestamp::Done() . + Timestamp ready_timestamp_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace mediapipe + +#endif // MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_SYNC_SET_INPUT_STREAM_HANDLER_H_
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/timestamp_align_input_stream_handler.cc b/third_party/mediapipe/src/mediapipe/framework/stream_handler/timestamp_align_input_stream_handler.cc index cb78618c..3e641b9b 100644 --- a/third_party/mediapipe/src/mediapipe/framework/stream_handler/timestamp_align_input_stream_handler.cc +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/timestamp_align_input_stream_handler.cc
@@ -12,92 +12,46 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "mediapipe/framework/stream_handler/timestamp_align_input_stream_handler.h" + #include <algorithm> +#include <functional> +#include <memory> #include <string> #include <utility> #include <vector> -#include "absl/log/absl_check.h" +#include "absl/log/check.h" #include "absl/strings/substitute.h" #include "absl/synchronization/mutex.h" +#include "mediapipe/framework/calculator_context_manager.h" +#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/input_stream_handler.h" +#include "mediapipe/framework/mediapipe_options.pb.h" #include "mediapipe/framework/stream_handler/timestamp_align_input_stream_handler.pb.h" #include "mediapipe/framework/timestamp.h" #include "mediapipe/framework/tool/validate_name.h" +#include "absl/log/absl_check.h" namespace mediapipe { -// The input streams must have the same time unit but may have different time -// origins (also called epochs). The timestamp_base_tag_index option -// designates an input stream as the timestamp base. -// -// TimestampAlignInputStreamHandler operates in two phases: -// -// 1. Pre-initialization: In this phase, the input stream handler passes -// through input packets in the timestamp base input stream, but buffers the -// input packets in all other input streams. This phase ends when the input -// stream handler has an input packet in every input stream. It uses the -// the timestamps of these input packets to calculate the timestamp offset of -// each input stream with respect to the timestamp base input stream. The -// timestamp offsets are saved for use in the next phase. -// -// 2. Post-initialization: In this phase, the input stream handler behaves -// like the DefaultInputStreamHandler, except that timestamp offsets are -// applied to the packet timestamps. -class TimestampAlignInputStreamHandler : public InputStreamHandler { - public: - TimestampAlignInputStreamHandler() = delete; - TimestampAlignInputStreamHandler(std::shared_ptr<tool::TagMap> tag_map, - CalculatorContextManager* cc_manager, - const MediaPipeOptions& options, - bool calculator_run_in_parallel); - - void PrepareForRun(std::function<void()> headers_ready_callback, - std::function<void()> notification_callback, - std::function<void(CalculatorContext*)> schedule_callback, - std::function<void(absl::Status)> error_callback) override; - - protected: - // In TimestampAlignInputStreamHandler, a node is "ready" if: - // - before the timestamp offsets are initialized: we have received a packet - // in the timestamp base input stream, or - // - after the timestamp offsets are initialized: the minimum bound (over - // all empty streams) is greater than the smallest timestamp of any - // stream, which means we have received all the packets that will be - // available at the next timestamp, or - // - all streams are done (need to call Close() in this case). - // Note that all packet timestamps and timestamp bounds are aligned with the - // timestamp base. - NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override; - - // Only invoked when associated GetNodeReadiness() returned kReadyForProcess. - void FillInputSet(Timestamp input_timestamp, - InputStreamShardSet* input_set) override; - - private: - CollectionItemId timestamp_base_stream_id_; - - absl::Mutex mutex_; - bool offsets_initialized_ ABSL_GUARDED_BY(mutex_) = false; - std::vector<TimestampDiff> timestamp_offsets_; -}; REGISTER_INPUT_STREAM_HANDLER(TimestampAlignInputStreamHandler); TimestampAlignInputStreamHandler::TimestampAlignInputStreamHandler( std::shared_ptr<tool::TagMap> tag_map, CalculatorContextManager* cc_manager, - const MediaPipeOptions& options, bool calculator_run_in_parallel) + const mediapipe::MediaPipeOptions& options, bool calculator_run_in_parallel) : InputStreamHandler(std::move(tag_map), cc_manager, options, calculator_run_in_parallel), timestamp_offsets_(input_stream_managers_.NumEntries()) { - const auto& handler_options = - options.GetExtension(TimestampAlignInputStreamHandlerOptions::ext); + const auto& handler_options = options.GetExtension( + mediapipe::TimestampAlignInputStreamHandlerOptions::ext); std::string tag; int index; MEDIAPIPE_CHECK_OK(tool::ParseTagIndex( handler_options.timestamp_base_tag_index(), &tag, &index)); timestamp_base_stream_id_ = input_stream_managers_.GetId(tag, index); - CHECK(timestamp_base_stream_id_.IsValid()) + ABSL_CHECK(timestamp_base_stream_id_.IsValid()) << "stream \"" << handler_options.timestamp_base_tag_index() << "\" is not found."; timestamp_offsets_[timestamp_base_stream_id_.value()] = 0; @@ -120,7 +74,7 @@ NodeReadiness TimestampAlignInputStreamHandler::GetNodeReadiness( Timestamp* min_stream_timestamp) { - DCHECK(min_stream_timestamp); + ABSL_DCHECK(min_stream_timestamp); *min_stream_timestamp = Timestamp::Done(); Timestamp min_bound = Timestamp::Done(); @@ -185,8 +139,8 @@ void TimestampAlignInputStreamHandler::FillInputSet( Timestamp input_timestamp, InputStreamShardSet* input_set) { - CHECK(input_timestamp.IsAllowedInStream()); - CHECK(input_set); + ABSL_CHECK(input_timestamp.IsAllowedInStream()); + ABSL_CHECK(input_set); { absl::MutexLock lock(&mutex_); if (!offsets_initialized_) {
diff --git a/third_party/mediapipe/src/mediapipe/framework/stream_handler/timestamp_align_input_stream_handler.h b/third_party/mediapipe/src/mediapipe/framework/stream_handler/timestamp_align_input_stream_handler.h new file mode 100644 index 0000000..dce8fad --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/framework/stream_handler/timestamp_align_input_stream_handler.h
@@ -0,0 +1,91 @@ +// Copyright 2023 The MediaPipe Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_TIMESTAMP_ALIGN_INPUT_STREAM_HANDLER_H_ +#define MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_TIMESTAMP_ALIGN_INPUT_STREAM_HANDLER_H_ + +#include <functional> +#include <memory> +#include <vector> + +#include "absl/base/thread_annotations.h" +#include "absl/status/status.h" +#include "absl/synchronization/mutex.h" +#include "mediapipe/framework/calculator_context_manager.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/collection_item_id.h" +#include "mediapipe/framework/input_stream_handler.h" +#include "mediapipe/framework/stream_handler/timestamp_align_input_stream_handler.pb.h" +#include "mediapipe/framework/timestamp.h" + +namespace mediapipe { + +// The input streams must have the same time unit but may have different time +// origins (also called epochs). The timestamp_base_tag_index option +// designates an input stream as the timestamp base. +// +// TimestampAlignInputStreamHandler operates in two phases: +// +// 1. Pre-initialization: In this phase, the input stream handler passes +// through input packets in the timestamp base input stream, but buffers the +// input packets in all other input streams. This phase ends when the input +// stream handler has an input packet in every input stream. It uses the +// the timestamps of these input packets to calculate the timestamp offset of +// each input stream with respect to the timestamp base input stream. The +// timestamp offsets are saved for use in the next phase. +// +// 2. Post-initialization: In this phase, the input stream handler behaves +// like the DefaultInputStreamHandler, except that timestamp offsets are +// applied to the packet timestamps. +class TimestampAlignInputStreamHandler : public InputStreamHandler { + public: + TimestampAlignInputStreamHandler() = delete; + TimestampAlignInputStreamHandler(std::shared_ptr<tool::TagMap> tag_map, + CalculatorContextManager* cc_manager, + const mediapipe::MediaPipeOptions& options, + bool calculator_run_in_parallel); + + void PrepareForRun(std::function<void()> headers_ready_callback, + std::function<void()> notification_callback, + std::function<void(CalculatorContext*)> schedule_callback, + std::function<void(absl::Status)> error_callback) override; + + protected: + // In TimestampAlignInputStreamHandler, a node is "ready" if: + // - before the timestamp offsets are initialized: we have received a packet + // in the timestamp base input stream, or + // - after the timestamp offsets are initialized: the minimum bound (over + // all empty streams) is greater than the smallest timestamp of any + // stream, which means we have received all the packets that will be + // available at the next timestamp, or + // - all streams are done (need to call Close() in this case). + // Note that all packet timestamps and timestamp bounds are aligned with the + // timestamp base. + NodeReadiness GetNodeReadiness(Timestamp* min_stream_timestamp) override; + + // Only invoked when associated GetNodeReadiness() returned kReadyForProcess. + void FillInputSet(Timestamp input_timestamp, + InputStreamShardSet* input_set) override; + + private: + CollectionItemId timestamp_base_stream_id_; + + absl::Mutex mutex_; + bool offsets_initialized_ ABSL_GUARDED_BY(mutex_) = false; + std::vector<TimestampDiff> timestamp_offsets_; +}; + +} // namespace mediapipe + +#endif // MEDIAPIPE_FRAMEWORK_STREAM_HANDLER_TIMESTAMP_ALIGN_INPUT_STREAM_HANDLER_H_
diff --git a/third_party/mediapipe/src/mediapipe/framework/timestamp.cc b/third_party/mediapipe/src/mediapipe/framework/timestamp.cc index 4ece74c..34d2a50 100644 --- a/third_party/mediapipe/src/mediapipe/framework/timestamp.cc +++ b/third_party/mediapipe/src/mediapipe/framework/timestamp.cc
@@ -17,6 +17,7 @@ #include <string> #include "absl/strings/str_cat.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -26,7 +27,7 @@ // - The safe int type will check for overflow/underflow and other errors. // - The CHECK in the constructor will disallow special values. TimestampDiff Timestamp::operator-(const Timestamp other) const { - CHECK(IsRangeValue() && other.IsRangeValue()) + ABSL_CHECK(IsRangeValue() && other.IsRangeValue()) << "This timestamp is " << DebugString() << " and other was " << other.DebugString(); TimestampBaseType tmp_base = timestamp_ - other.timestamp_; @@ -43,7 +44,7 @@ // Clamp the addition to the range [Timestamp::Min(), Timestamp::Max()]. Timestamp Timestamp::operator+(const TimestampDiff offset) const { - CHECK(IsRangeValue()) << "Timestamp is: " << DebugString(); + ABSL_CHECK(IsRangeValue()) << "Timestamp is: " << DebugString(); TimestampBaseType offset_base(offset.Value()); if (offset_base >= TimestampBaseType(0)) { if (timestamp_.value() >= Timestamp::Max().Value() - offset_base.value()) {
diff --git a/third_party/mediapipe/src/mediapipe/framework/timestamp.h b/third_party/mediapipe/src/mediapipe/framework/timestamp.h index 8949dcc..6c59c5c 100644 --- a/third_party/mediapipe/src/mediapipe/framework/timestamp.h +++ b/third_party/mediapipe/src/mediapipe/framework/timestamp.h
@@ -47,10 +47,10 @@ #include <cmath> #include <string> -#include "absl/log/absl_check.h" #include "mediapipe/framework/deps/safe_int.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/framework/tool/BUILD b/third_party/mediapipe/src/mediapipe/framework/tool/BUILD index b7c563b..8e1ef94a 100644 --- a/third_party/mediapipe/src/mediapipe/framework/tool/BUILD +++ b/third_party/mediapipe/src/mediapipe/framework/tool/BUILD
@@ -335,6 +335,7 @@ cc_library( name = "packet_generator_wrapper_calculator", srcs = ["packet_generator_wrapper_calculator.cc"], + hdrs = ["packet_generator_wrapper_calculator.h"], visibility = ["//mediapipe/framework:__subpackages__"], deps = [ ":packet_generator_wrapper_calculator_cc_proto", @@ -342,6 +343,9 @@ "//mediapipe/framework:calculator_registry", "//mediapipe/framework:output_side_packet", "//mediapipe/framework:packet_generator", + "//mediapipe/framework:packet_set", + "//mediapipe/framework/port:status", + "@com_google_absl//absl/status", ], alwayslink = 1, ) @@ -386,21 +390,22 @@ visibility = ["//visibility:public"], deps = [ ":name_util", + ":status_util", "//mediapipe/calculators/internal:callback_packet_calculator", "//mediapipe/calculators/internal:callback_packet_calculator_cc_proto", "//mediapipe/framework:calculator_base", "//mediapipe/framework:calculator_cc_proto", "//mediapipe/framework:calculator_graph", "//mediapipe/framework:calculator_registry", - "//mediapipe/framework:input_stream", "//mediapipe/framework:packet", "//mediapipe/framework:packet_type", - "//mediapipe/framework/port:logging", + "//mediapipe/framework:timestamp", "//mediapipe/framework/port:source_location", "//mediapipe/framework/port:status", "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/status", "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", ], alwayslink = 1, ) @@ -501,7 +506,6 @@ ":calculator_graph_template_cc_proto", ":proto_util_lite", "//mediapipe/framework:calculator_cc_proto", - "//mediapipe/framework/port:logging", "//mediapipe/framework/port:numbers", "//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:status",
diff --git a/third_party/mediapipe/src/mediapipe/framework/tool/packet_generator_wrapper_calculator.cc b/third_party/mediapipe/src/mediapipe/framework/tool/packet_generator_wrapper_calculator.cc index 831918d..07eae6f 100644 --- a/third_party/mediapipe/src/mediapipe/framework/tool/packet_generator_wrapper_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/framework/tool/packet_generator_wrapper_calculator.cc
@@ -1,52 +1,55 @@ +#include "mediapipe/framework/tool/packet_generator_wrapper_calculator.h" + +#include "absl/status/status.h" #include "mediapipe/framework/calculator_base.h" #include "mediapipe/framework/calculator_registry.h" #include "mediapipe/framework/output_side_packet.h" #include "mediapipe/framework/packet_generator.h" +#include "mediapipe/framework/packet_set.h" +#include "mediapipe/framework/port/status_macros.h" #include "mediapipe/framework/tool/packet_generator_wrapper_calculator.pb.h" namespace mediapipe { -class PacketGeneratorWrapperCalculator : public CalculatorBase { - public: - static absl::Status GetContract(CalculatorContract* cc) { - const auto& options = - cc->Options<::mediapipe::PacketGeneratorWrapperCalculatorOptions>(); - ASSIGN_OR_RETURN(auto static_access, - mediapipe::internal::StaticAccessToGeneratorRegistry:: - CreateByNameInNamespace(options.package(), - options.packet_generator())); - MP_RETURN_IF_ERROR(static_access->FillExpectations( - options.options(), &cc->InputSidePackets(), - &cc->OutputSidePackets())) - .SetPrepend() - << options.packet_generator() << "::FillExpectations() failed: "; - return absl::OkStatus(); - } +absl::Status PacketGeneratorWrapperCalculator::GetContract( + CalculatorContract* cc) { + const auto& options = + cc->Options<::mediapipe::PacketGeneratorWrapperCalculatorOptions>(); + ASSIGN_OR_RETURN(auto static_access, + mediapipe::internal::StaticAccessToGeneratorRegistry:: + CreateByNameInNamespace(options.package(), + options.packet_generator())); + MP_RETURN_IF_ERROR(static_access->FillExpectations(options.options(), + &cc->InputSidePackets(), + &cc->OutputSidePackets())) + .SetPrepend() + << options.packet_generator() << "::FillExpectations() failed: "; + return absl::OkStatus(); +} - absl::Status Open(CalculatorContext* cc) override { - const auto& options = - cc->Options<::mediapipe::PacketGeneratorWrapperCalculatorOptions>(); - ASSIGN_OR_RETURN(auto static_access, - mediapipe::internal::StaticAccessToGeneratorRegistry:: - CreateByNameInNamespace(options.package(), - options.packet_generator())); - mediapipe::PacketSet output_packets(cc->OutputSidePackets().TagMap()); - MP_RETURN_IF_ERROR(static_access->Generate(options.options(), - cc->InputSidePackets(), - &output_packets)) - .SetPrepend() - << options.packet_generator() << "::Generate() failed: "; - for (auto id = output_packets.BeginId(); id < output_packets.EndId(); - ++id) { - cc->OutputSidePackets().Get(id).Set(output_packets.Get(id)); - } - return absl::OkStatus(); +absl::Status PacketGeneratorWrapperCalculator::Open(CalculatorContext* cc) { + const auto& options = + cc->Options<::mediapipe::PacketGeneratorWrapperCalculatorOptions>(); + ASSIGN_OR_RETURN(auto static_access, + mediapipe::internal::StaticAccessToGeneratorRegistry:: + CreateByNameInNamespace(options.package(), + options.packet_generator())); + mediapipe::PacketSet output_packets(cc->OutputSidePackets().TagMap()); + MP_RETURN_IF_ERROR(static_access->Generate(options.options(), + cc->InputSidePackets(), + &output_packets)) + .SetPrepend() + << options.packet_generator() << "::Generate() failed: "; + for (auto id = output_packets.BeginId(); id < output_packets.EndId(); ++id) { + cc->OutputSidePackets().Get(id).Set(output_packets.Get(id)); } + return absl::OkStatus(); +} - absl::Status Process(CalculatorContext* cc) override { - return absl::OkStatus(); - } -}; +absl::Status PacketGeneratorWrapperCalculator::Process(CalculatorContext* cc) { + return absl::OkStatus(); +} + REGISTER_CALCULATOR(PacketGeneratorWrapperCalculator); } // namespace mediapipe
diff --git a/third_party/mediapipe/src/mediapipe/framework/tool/packet_generator_wrapper_calculator.h b/third_party/mediapipe/src/mediapipe/framework/tool/packet_generator_wrapper_calculator.h new file mode 100644 index 0000000..012281c --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/framework/tool/packet_generator_wrapper_calculator.h
@@ -0,0 +1,32 @@ +// Copyright 2023 The MediaPipe Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIAPIPE_FRAMEWORK_TOOL_PACKET_GENERATOR_WRAPPER_CALCULATOR_H_ +#define MEDIAPIPE_FRAMEWORK_TOOL_PACKET_GENERATOR_WRAPPER_CALCULATOR_H_ + +#include "absl/status/status.h" +#include "mediapipe/framework/calculator_base.h" + +namespace mediapipe { + +class PacketGeneratorWrapperCalculator : public CalculatorBase { + public: + static absl::Status GetContract(CalculatorContract* cc); + absl::Status Open(CalculatorContext* cc) override; + absl::Status Process(CalculatorContext* cc) override; +}; + +} // namespace mediapipe + +#endif // MEDIAPIPE_FRAMEWORK_TOOL_PACKET_GENERATOR_WRAPPER_CALCULATOR_H_
diff --git a/third_party/mediapipe/src/mediapipe/framework/tool/proto_util_lite.cc b/third_party/mediapipe/src/mediapipe/framework/tool/proto_util_lite.cc index 745f4a1..59387e5 100644 --- a/third_party/mediapipe/src/mediapipe/framework/tool/proto_util_lite.cc +++ b/third_party/mediapipe/src/mediapipe/framework/tool/proto_util_lite.cc
@@ -25,6 +25,7 @@ #include "mediapipe/framework/port/statusor.h" #include "mediapipe/framework/tool/field_data.pb.h" #include "mediapipe/framework/type_map.h" +#include "absl/log/absl_check.h" #define RET_CHECK_NO_LOG(cond) RET_CHECK(cond).SetNoLogging() @@ -411,7 +412,7 @@ } case W::TYPE_GROUP: case W::TYPE_MESSAGE: - CHECK(false) << "DeserializeValue cannot deserialize a Message."; + ABSL_CHECK(false) << "DeserializeValue cannot deserialize a Message."; case W::TYPE_UINT32: return ReadPrimitive<uint32_t, W::TYPE_UINT32>(&input, result); case W::TYPE_ENUM:
diff --git a/third_party/mediapipe/src/mediapipe/framework/tool/sink.cc b/third_party/mediapipe/src/mediapipe/framework/tool/sink.cc index f8abf49..738a45f9 100644 --- a/third_party/mediapipe/src/mediapipe/framework/tool/sink.cc +++ b/third_party/mediapipe/src/mediapipe/framework/tool/sink.cc
@@ -18,60 +18,65 @@ #include "mediapipe/framework/tool/sink.h" +#include <stdio.h> + +#include <functional> +#include <map> #include <memory> +#include <string> #include <utility> #include <vector> +#include "absl/log/check.h" +#include "absl/status/status.h" #include "absl/strings/str_cat.h" -#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" #include "mediapipe/calculators/internal/callback_packet_calculator.pb.h" #include "mediapipe/framework/calculator.pb.h" #include "mediapipe/framework/calculator_base.h" #include "mediapipe/framework/calculator_graph.h" #include "mediapipe/framework/calculator_registry.h" -#include "mediapipe/framework/input_stream.h" #include "mediapipe/framework/packet.h" #include "mediapipe/framework/packet_type.h" -#include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/source_location.h" #include "mediapipe/framework/port/status_builder.h" +#include "mediapipe/framework/timestamp.h" #include "mediapipe/framework/tool/name_util.h" +#include "mediapipe/framework/tool/status_util.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace tool { -namespace { -// Produces an output packet with the PostStream timestamp containing the -// input side packet. -class MediaPipeInternalSidePacketToPacketStreamCalculator - : public CalculatorBase { - public: - static absl::Status GetContract(CalculatorContract* cc) { - cc->InputSidePackets().Index(0).SetAny(); - cc->Outputs().Index(0).SetSameAs(&cc->InputSidePackets().Index(0)); - return absl::OkStatus(); - } - absl::Status Open(CalculatorContext* cc) final { - cc->Outputs().Index(0).AddPacket( - cc->InputSidePackets().Index(0).At(Timestamp::PostStream())); - cc->Outputs().Index(0).Close(); - return absl::OkStatus(); - } +absl::Status MediaPipeInternalSidePacketToPacketStreamCalculator::GetContract( + CalculatorContract* cc) { + cc->InputSidePackets().Index(0).SetAny(); + cc->Outputs().Index(0).SetSameAs(&cc->InputSidePackets().Index(0)); + return absl::OkStatus(); +} - absl::Status Process(CalculatorContext* cc) final { - // The framework treats this calculator as a source calculator. - return mediapipe::tool::StatusStop(); - } -}; +absl::Status MediaPipeInternalSidePacketToPacketStreamCalculator::Open( + CalculatorContext* cc) { + cc->Outputs().Index(0).AddPacket( + cc->InputSidePackets().Index(0).At(Timestamp::PostStream())); + cc->Outputs().Index(0).Close(); + return absl::OkStatus(); +} + +absl::Status MediaPipeInternalSidePacketToPacketStreamCalculator::Process( + CalculatorContext* cc) { + // The framework treats this calculator as a source calculator. + return mediapipe::tool::StatusStop(); +} + REGISTER_CALCULATOR(MediaPipeInternalSidePacketToPacketStreamCalculator); -} // namespace void AddVectorSink(const std::string& stream_name, // CalculatorGraphConfig* config, // std::vector<Packet>* dumped_data) { - CHECK(config); - CHECK(dumped_data); + ABSL_CHECK(config); + ABSL_CHECK(dumped_data); std::string input_side_packet_name; tool::AddCallbackCalculator(stream_name, config, &input_side_packet_name, @@ -90,15 +95,15 @@ // Up to 64-bit pointer in hex (16 characters) and an optional "0x" prepended. char address[19]; int written = snprintf(address, sizeof(address), "%p", dumped_data); - CHECK(written > 0 && written < sizeof(address)); + ABSL_CHECK(written > 0 && written < sizeof(address)); options->set_pointer(address); } void AddPostStreamPacketSink(const std::string& stream_name, CalculatorGraphConfig* config, Packet* post_stream_packet) { - CHECK(config); - CHECK(post_stream_packet); + ABSL_CHECK(config); + ABSL_CHECK(post_stream_packet); std::string input_side_packet_name; tool::AddCallbackCalculator(stream_name, config, &input_side_packet_name, @@ -116,14 +121,14 @@ // Up to 64-bit pointer in hex (16 characters) and an optional "0x" prepended. char address[19]; int written = snprintf(address, sizeof(address), "%p", post_stream_packet); - CHECK(written > 0 && written < sizeof(address)); + ABSL_CHECK(written > 0 && written < sizeof(address)); options->set_pointer(address); } void AddSidePacketSink(const std::string& side_packet_name, CalculatorGraphConfig* config, Packet* dumped_packet) { - CHECK(config); - CHECK(dumped_packet); + ABSL_CHECK(config); + ABSL_CHECK(dumped_packet); CalculatorGraphConfig::Node* conversion_node = config->add_node(); const std::string node_name = GetUnusedNodeName( @@ -145,8 +150,8 @@ CalculatorGraphConfig* config, std::string* callback_side_packet_name, bool use_std_function) { - CHECK(config); - CHECK(callback_side_packet_name); + ABSL_CHECK(config); + ABSL_CHECK(callback_side_packet_name); CalculatorGraphConfig::Node* sink_node = config->add_node(); sink_node->set_name(GetUnusedNodeName( *config, @@ -182,8 +187,8 @@ std::function<void(const std::vector<Packet>&)> callback, CalculatorGraphConfig* config, std::map<std::string, Packet>* side_packets, bool observe_timestamp_bounds) { - CHECK(config); - CHECK(side_packets); + ABSL_CHECK(config); + ABSL_CHECK(side_packets); CalculatorGraphConfig::Node* sink_node = config->add_node(); const std::string name = GetUnusedNodeName( *config, absl::StrCat("multi_callback_", absl::StrJoin(streams, "_"))); @@ -217,8 +222,8 @@ CalculatorGraphConfig* config, std::string* callback_side_packet_name, bool use_std_function) { - CHECK(config); - CHECK(callback_side_packet_name); + ABSL_CHECK(config); + ABSL_CHECK(callback_side_packet_name); CalculatorGraphConfig::Node* sink_node = config->add_node(); sink_node->set_name(GetUnusedNodeName( *config, @@ -326,7 +331,7 @@ cc->Inputs().Tag("HEADER").SetAny(); if (cc->InputSidePackets().UsesTags()) { - CHECK(cc->InputSidePackets().HasTag("CALLBACK")); + ABSL_CHECK(cc->InputSidePackets().HasTag("CALLBACK")); cc->InputSidePackets() .Tag("CALLBACK") .Set<std::function<void(const Packet&, const Packet&)>>();
diff --git a/third_party/mediapipe/src/mediapipe/framework/tool/sink.h b/third_party/mediapipe/src/mediapipe/framework/tool/sink.h index f786e60..3982bc34 100644 --- a/third_party/mediapipe/src/mediapipe/framework/tool/sink.h +++ b/third_party/mediapipe/src/mediapipe/framework/tool/sink.h
@@ -28,13 +28,16 @@ #ifndef MEDIAPIPE_FRAMEWORK_TOOL_SINK_H_ #define MEDIAPIPE_FRAMEWORK_TOOL_SINK_H_ +#include <functional> #include <string> #include <vector> #include "absl/base/macros.h" +#include "absl/status/status.h" #include "mediapipe/framework/calculator_base.h" #include "mediapipe/framework/packet_type.h" #include "mediapipe/framework/port/status.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -205,6 +208,16 @@ Packet header_packet_; }; +// Produces an output packet with the PostStream timestamp containing the +// input side packet. +class MediaPipeInternalSidePacketToPacketStreamCalculator + : public CalculatorBase { + public: + static absl::Status GetContract(CalculatorContract* cc); + absl::Status Open(CalculatorContext* cc) final; + absl::Status Process(CalculatorContext* cc) final; +}; + } // namespace tool } // namespace mediapipe
diff --git a/third_party/mediapipe/src/mediapipe/framework/tool/switch_container.cc b/third_party/mediapipe/src/mediapipe/framework/tool/switch_container.cc index daa1299..bb17a914 100644 --- a/third_party/mediapipe/src/mediapipe/framework/tool/switch_container.cc +++ b/third_party/mediapipe/src/mediapipe/framework/tool/switch_container.cc
@@ -31,6 +31,7 @@ #include "mediapipe/framework/tool/name_util.h" #include "mediapipe/framework/tool/subgraph_expansion.h" #include "mediapipe/framework/tool/switch_container.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace tool { @@ -148,7 +149,7 @@ // Returns an unused name similar to a specified name. std::string UniqueName(std::string name, std::set<std::string>* names) { - CHECK(names != nullptr); + ABSL_CHECK(names != nullptr); std::string result = name; int suffix = 2; while (names->count(result) > 0) { @@ -161,7 +162,7 @@ // Parses tag, index, and name from a list of stream identifiers. void ParseTags(const proto_ns::RepeatedPtrField<std::string>& streams, std::map<TagIndex, std::string>* result) { - CHECK(result != nullptr); + ABSL_CHECK(result != nullptr); std::set<std::string> used_names; int used_index = -1; for (const std::string& stream : streams) { @@ -177,14 +178,14 @@ // Removes the entry for a tag and index from a map. void EraseTag(const std::string& stream, std::map<TagIndex, std::string>* streams) { - CHECK(streams != nullptr); + ABSL_CHECK(streams != nullptr); streams->erase(ParseTagIndexFromStream(absl::StrCat(stream, ":u"))); } // Removes the entry for a tag and index from a list. void EraseTag(const std::string& stream, proto_ns::RepeatedPtrField<std::string>* streams) { - CHECK(streams != nullptr); + ABSL_CHECK(streams != nullptr); TagIndex stream_tag = ParseTagIndexFromStream(absl::StrCat(stream, ":u")); for (int i = streams->size() - 1; i >= 0; --i) { TagIndex tag = ParseTagIndexFromStream(streams->at(i)); @@ -197,7 +198,7 @@ // Returns the stream names for the container node. void GetContainerNodeStreams(const CalculatorGraphConfig::Node& node, CalculatorGraphConfig::Node* result) { - CHECK(result != nullptr); + ABSL_CHECK(result != nullptr); *result->mutable_input_stream() = node.input_stream(); *result->mutable_output_stream() = node.output_stream(); *result->mutable_input_side_packet() = node.input_side_packet();
diff --git a/third_party/mediapipe/src/mediapipe/framework/tool/template_expander.cc b/third_party/mediapipe/src/mediapipe/framework/tool/template_expander.cc index a91ea5a..ef217b06 100644 --- a/third_party/mediapipe/src/mediapipe/framework/tool/template_expander.cc +++ b/third_party/mediapipe/src/mediapipe/framework/tool/template_expander.cc
@@ -15,25 +15,20 @@ #include "mediapipe/framework/tool/template_expander.h" #include <algorithm> -#include <map> #include <memory> #include <string> -#include <utility> #include <vector> #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" -#include "absl/strings/str_join.h" -#include "absl/strings/str_split.h" #include "mediapipe/framework/calculator.pb.h" -#include "mediapipe/framework/port/canonical_errors.h" -#include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/numbers.h" #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/tool/calculator_graph_template.pb.h" #include "mediapipe/framework/tool/proto_util_lite.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -183,8 +178,7 @@ int FieldCount(const FieldValue& base, ProtoPath field_path, FieldType field_type) { int result = 0; - CHECK( - ProtoUtilLite::GetFieldCount(base, field_path, field_type, &result).ok()); + CHECK_OK(ProtoUtilLite::GetFieldCount(base, field_path, field_type, &result)); return result; } @@ -647,7 +641,7 @@ for (int i = 0; i < args.size(); ++i) { if (args[i].has_dict()) { FieldValue dict_bytes; - CHECK(args[i].dict().SerializePartialToString(&dict_bytes)); + ABSL_CHECK(args[i].dict().SerializePartialToString(&dict_bytes)); result->push_back(dict_bytes); } else if (args[i].has_num() || args[i].has_str()) { std::string text_value = args[i].has_num()
diff --git a/third_party/mediapipe/src/mediapipe/framework/tool/template_parser.cc b/third_party/mediapipe/src/mediapipe/framework/tool/template_parser.cc index 743df9fb..74f5bce6 100644 --- a/third_party/mediapipe/src/mediapipe/framework/tool/template_parser.cc +++ b/third_party/mediapipe/src/mediapipe/framework/tool/template_parser.cc
@@ -37,6 +37,7 @@ #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/tool/calculator_graph_template.pb.h" #include "mediapipe/framework/tool/proto_util_lite.h" +#include "absl/log/absl_check.h" using mediapipe::proto_ns::Descriptor; using mediapipe::proto_ns::DynamicMessageFactory; @@ -471,7 +472,7 @@ "\" stored in google.protobuf.Any."); return false; } - DO(ConsumeAnyValue(value_descriptor, &serialized_value)); + DO(ConsumeAnyValue(any_value_field, value_descriptor, &serialized_value)); if (singular_overwrite_policy_ == FORBID_SINGULAR_OVERWRITES) { // Fail if any_type_url_field has already been specified. if ((!any_type_url_field->is_repeated() && @@ -565,7 +566,7 @@ // Skips unknown or reserved fields. if (field == NULL) { - CHECK(allow_unknown_field_ || allow_unknown_extension_ || reserved_field); + ABSL_CHECK(allow_unknown_field_ || allow_unknown_extension_ || reserved_field); // Try to guess the type of this field. // If this field is not a message, there should be a ":" between the @@ -709,7 +710,7 @@ // If the parse information tree is not NULL, create a nested one // for the nested message. ParseInfoTree* parent = parse_info_tree_; - if (parent != NULL) { + if (parent) { parse_info_tree_ = parent->CreateNested(field); } @@ -1191,8 +1192,20 @@ // A helper function for reconstructing Any::value. Consumes a text of // full_type_name, then serializes it into serialized_value. - bool ConsumeAnyValue(const Descriptor* value_descriptor, + bool ConsumeAnyValue(const FieldDescriptor* field, + const Descriptor* value_descriptor, std::string* serialized_value) { + if (--recursion_limit_ < 0) { + ReportError("Message is too deep"); + return false; + } + // If the parse information tree is not NULL, create a nested one + // for the nested message. + ParseInfoTree* parent = parse_info_tree_; + if (parent) { + parse_info_tree_ = parent->CreateNested(field); + } + DynamicMessageFactory factory; const Message* value_prototype = factory.GetPrototype(value_descriptor); if (value_prototype == NULL) { @@ -1214,6 +1227,11 @@ } value->AppendToString(serialized_value); } + + ++recursion_limit_; + + // Reset the parse information tree. + parse_info_tree_ = parent; return true; } @@ -1380,7 +1398,7 @@ void SerializeField(const Message* message, const FieldDescriptor* field, std::vector<ProtoUtilLite::FieldValue>* result) { ProtoUtilLite::FieldValue message_bytes; - CHECK(DeterministicallySerialize(*message, &message_bytes)); + ABSL_CHECK(DeterministicallySerialize(*message, &message_bytes)); ProtoUtilLite::FieldAccess access( field->number(), static_cast<ProtoUtilLite::FieldType>(field->type())); MEDIAPIPE_CHECK_OK(access.SetMessage(message_bytes)); @@ -1685,13 +1703,13 @@ const std::vector<ProtoUtilLite::FieldValue>& args) { auto field_type = static_cast<ProtoUtilLite::FieldType>(field->type()); ProtoUtilLite::FieldValue message_bytes; - CHECK(message->SerializePartialToString(&message_bytes)); + ABSL_CHECK(message->SerializePartialToString(&message_bytes)); int count; MEDIAPIPE_CHECK_OK(ProtoUtilLite::GetFieldCount( message_bytes, {{field->number(), 0}}, field_type, &count)); MEDIAPIPE_CHECK_OK(ProtoUtilLite::ReplaceFieldRange( &message_bytes, {{field->number(), count}}, 0, field_type, args)); - CHECK(message->ParsePartialFromString(message_bytes)); + ABSL_CHECK(message->ParsePartialFromString(message_bytes)); } // Parse and record a template definition for the current field path.
diff --git a/third_party/mediapipe/src/mediapipe/framework/tool/validate_type.cc b/third_party/mediapipe/src/mediapipe/framework/tool/validate_type.cc index 4c97a310..a5a258d 100644 --- a/third_party/mediapipe/src/mediapipe/framework/tool/validate_type.cc +++ b/third_party/mediapipe/src/mediapipe/framework/tool/validate_type.cc
@@ -34,6 +34,7 @@ #include "absl/synchronization/blocking_counter.h" #include "mediapipe/framework/port/threadpool.h" #include "mediapipe/util/cpu_util.h" +#include "absl/log/absl_check.h" #endif // !MEDIAPIPE_MOBILE && !MEDIAPIPE_LITE namespace mediapipe { @@ -78,7 +79,7 @@ const PacketGeneratorOptions& extendable_options, const PacketSet& input_side_packets, PacketSet* output_side_packets, const std::string& package) { - CHECK(output_side_packets); + ABSL_CHECK(output_side_packets); // Get static access to functions. ASSIGN_OR_RETURN( auto static_access,
diff --git a/third_party/mediapipe/src/mediapipe/framework/type_map.h b/third_party/mediapipe/src/mediapipe/framework/type_map.h index f958a658..eafcd1f 100644 --- a/third_party/mediapipe/src/mediapipe/framework/type_map.h +++ b/third_party/mediapipe/src/mediapipe/framework/type_map.h
@@ -64,12 +64,12 @@ #include <vector> #include "absl/base/macros.h" -#include "absl/log/absl_check.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/demangle.h" #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/tool/status_util.h" #include "mediapipe/framework/tool/type_util.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/framework/validated_graph_config.cc b/third_party/mediapipe/src/mediapipe/framework/validated_graph_config.cc index 15eac320..c614c35f 100644 --- a/third_party/mediapipe/src/mediapipe/framework/validated_graph_config.cc +++ b/third_party/mediapipe/src/mediapipe/framework/validated_graph_config.cc
@@ -46,6 +46,7 @@ #include "mediapipe/framework/tool/subgraph_expansion.h" #include "mediapipe/framework/tool/validate.h" #include "mediapipe/framework/tool/validate_name.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -744,7 +745,7 @@ case NodeTypeInfo::NodeType::CALCULATOR: return generators_.size() + node.index; default: - CHECK(false); + ABSL_CHECK(false); } }
diff --git a/third_party/mediapipe/src/mediapipe/gpu/MPPMetalHelper.mm b/third_party/mediapipe/src/mediapipe/gpu/MPPMetalHelper.mm index db8bb0a..3a6bab2 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/MPPMetalHelper.mm +++ b/third_party/mediapipe/src/mediapipe/gpu/MPPMetalHelper.mm
@@ -20,8 +20,8 @@ #import "mediapipe/gpu/metal_shared_resources.h" #import "GTMDefines.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/ret_check.h" +#include "absl/log/absl_check.h" @interface MPPMetalHelper () { mediapipe::GpuResources* _gpuResources;
diff --git a/third_party/mediapipe/src/mediapipe/gpu/cv_pixel_buffer_pool_wrapper.cc b/third_party/mediapipe/src/mediapipe/gpu/cv_pixel_buffer_pool_wrapper.cc index b0689cd6..90af291 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/cv_pixel_buffer_pool_wrapper.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/cv_pixel_buffer_pool_wrapper.cc
@@ -17,10 +17,10 @@ #include <tuple> #include "CoreFoundation/CFBase.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/objc/CFHolder.h" #include "mediapipe/objc/util.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -59,7 +59,7 @@ ++threshold; } } - CHECK(!err) << "Error creating pixel buffer: " << err; + ABSL_CHECK(!err) << "Error creating pixel buffer: " << err; count_ = threshold; return MakeCFHolderAdopting(buffer); } @@ -78,7 +78,7 @@ CVPixelBufferRef buffer; CVReturn err = CreateCVPixelBufferWithoutPool(spec.width, spec.height, cv_format, &buffer); - CHECK(!err) << "Error creating pixel buffer: " << err; + ABSL_CHECK(!err) << "Error creating pixel buffer: " << err; return MakeCFHolderAdopting(buffer); }
diff --git a/third_party/mediapipe/src/mediapipe/gpu/cv_texture_cache_manager.cc b/third_party/mediapipe/src/mediapipe/gpu/cv_texture_cache_manager.cc index 5e06ab6..8f5e285 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/cv_texture_cache_manager.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/cv_texture_cache_manager.cc
@@ -14,8 +14,8 @@ #include "mediapipe/gpu/cv_texture_cache_manager.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -33,7 +33,7 @@ void CvTextureCacheManager::RegisterTextureCache(CVTextureCacheType cache) { absl::MutexLock lock(&mutex_); - CHECK(std::find(texture_caches_.begin(), texture_caches_.end(), cache) == + ABSL_CHECK(std::find(texture_caches_.begin(), texture_caches_.end(), cache) == texture_caches_.end()) << "Attempting to register a texture cache twice"; texture_caches_.emplace_back(cache); @@ -43,7 +43,7 @@ absl::MutexLock lock(&mutex_); auto it = std::find(texture_caches_.begin(), texture_caches_.end(), cache); - CHECK(it != texture_caches_.end()) + ABSL_CHECK(it != texture_caches_.end()) << "Attempting to unregister an unknown texture cache"; texture_caches_.erase(it); }
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gl_calculator_helper.cc b/third_party/mediapipe/src/mediapipe/gpu/gl_calculator_helper.cc index 0a422f3..cd8d7db 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gl_calculator_helper.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gl_calculator_helper.cc
@@ -14,7 +14,6 @@ #include "mediapipe/gpu/gl_calculator_helper.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/formats/image.h" #include "mediapipe/framework/formats/image_frame.h" #include "mediapipe/framework/legacy_calculator_support.h" @@ -23,6 +22,7 @@ #include "mediapipe/framework/port/status.h" #include "mediapipe/gpu/gpu_buffer.h" #include "mediapipe/gpu/gpu_service.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -37,7 +37,7 @@ } absl::Status GlCalculatorHelper::Open(CalculatorContext* cc) { - CHECK(cc); + ABSL_CHECK(cc); auto gpu_service = cc->Service(kGpuService); RET_CHECK(gpu_service.IsAvailable()) << "GPU service not available. Did you forget to call " @@ -195,8 +195,8 @@ void GlCalculatorHelper::GetGpuBufferDimensions(const GpuBuffer& pixel_buffer, int* width, int* height) { - CHECK(width); - CHECK(height); + ABSL_CHECK(width); + ABSL_CHECK(height); *width = pixel_buffer.width(); *height = pixel_buffer.height(); }
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gl_context.cc b/third_party/mediapipe/src/mediapipe/gpu/gl_context.cc index c596430..307af799 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gl_context.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gl_context.cc
@@ -34,8 +34,8 @@ #ifndef __EMSCRIPTEN__ #include "absl/debugging/leak_check.h" -#include "absl/log/absl_check.h" #include "mediapipe/gpu/gl_thread_collector.h" +#include "absl/log/absl_check.h" #endif #ifndef GL_MAJOR_VERSION @@ -75,7 +75,7 @@ GlContext::DedicatedThread::~DedicatedThread() { if (IsCurrentThread()) { - CHECK(self_destruct_); + ABSL_CHECK(self_destruct_); ABSL_CHECK_EQ(pthread_detach(gl_thread_id_), 0); } else { // Give an invalid job to signal termination. @@ -169,7 +169,7 @@ // non-calculator tasks in the presence of GL source calculators, calculator // tasks must always be scheduled as new tasks, or another solution needs to // be set up to avoid starvation. See b/78522434. - CHECK(gl_func); + ABSL_CHECK(gl_func); PutJob(std::move(gl_func)); } @@ -495,10 +495,10 @@ } // Check that the context object is consistent with the native context. if (old_context_obj && saved_context) { - DCHECK(old_context_obj->context_ == saved_context->context); + ABSL_DCHECK(old_context_obj->context_ == saved_context->context); } if (new_context_obj) { - DCHECK(new_context_obj->context_ == new_context.context); + ABSL_DCHECK(new_context_obj->context_ == new_context.context); } if (new_context_obj && (old_context_obj == new_context_obj)) { @@ -538,7 +538,7 @@ } absl::Status GlContext::EnterContext(ContextBinding* saved_context) { - DCHECK(HasContext()); + ABSL_DCHECK(HasContext()); return SwitchContext(saved_context, ThisContextBinding()); } @@ -849,7 +849,7 @@ std::shared_ptr<GlSyncPoint> GlContext::CreateSyncTokenForCurrentExternalContext( const std::shared_ptr<GlContext>& delegate_graph_context) { - CHECK(delegate_graph_context); + ABSL_CHECK(delegate_graph_context); if (!IsAnyContextCurrent()) return nullptr; if (delegate_graph_context->ShouldUseFenceSync()) { return std::shared_ptr<GlSyncPoint>( @@ -900,7 +900,7 @@ // from the GlContext, and we must wait for gl_finish_count_ to pass it. // Therefore, we need to do at most one more glFinish call. This DCHECK // is used for documentation and sanity-checking purposes. - DCHECK(gl_finish_count_ >= count_to_pass); + ABSL_DCHECK(gl_finish_count_ >= count_to_pass); if (gl_finish_count_ == count_to_pass) { glFinish(); GlFinishCalled(); @@ -921,7 +921,7 @@ // it can signal the right condition variable if it is asked to do a // glFinish. absl::MutexLock other_lock(&other->mutex_); - DCHECK(!other->context_waiting_on_); + ABSL_DCHECK(!other->context_waiting_on_); other->context_waiting_on_ = this; } // We do not schedule this action using Run because we don't necessarily @@ -965,12 +965,12 @@ } void GlContext::WaitSyncToken(const std::shared_ptr<GlSyncPoint>& token) { - CHECK(token); + ABSL_CHECK(token); token->Wait(); } bool GlContext::SyncTokenIsReady(const std::shared_ptr<GlSyncPoint>& token) { - CHECK(token); + ABSL_CHECK(token); return token->IsReady(); } @@ -1032,7 +1032,7 @@ const GlTextureInfo& GlTextureInfoForGpuBufferFormat(GpuBufferFormat format, int plane) { std::shared_ptr<GlContext> ctx = GlContext::GetCurrent(); - CHECK(ctx != nullptr); + ABSL_CHECK(ctx != nullptr); return GlTextureInfoForGpuBufferFormat(format, plane, ctx->GetGlVersion()); }
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gl_context.h b/third_party/mediapipe/src/mediapipe/gpu/gl_context.h index fba0267..9f6bc17a 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gl_context.h +++ b/third_party/mediapipe/src/mediapipe/gpu/gl_context.h
@@ -37,6 +37,7 @@ #include <CoreVideo/CoreVideo.h> #include "mediapipe/objc/CFHolder.h" +#include "absl/log/absl_check.h" #if TARGET_OS_OSX @@ -295,7 +296,7 @@ // TOOD: const result? template <class T> T& GetCachedAttachment(const Attachment<T>& attachment) { - DCHECK(IsCurrent()); + ABSL_DCHECK(IsCurrent()); internal::AttachmentPtr<void>& entry = attachments_[&attachment]; if (entry == nullptr) { entry = attachment.factory()(*this);
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gl_context_egl.cc b/third_party/mediapipe/src/mediapipe/gpu/gl_context_egl.cc index f8784bb..4117d8a0 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gl_context_egl.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gl_context_egl.cc
@@ -23,6 +23,7 @@ #include "mediapipe/framework/port/status_builder.h" #include "mediapipe/gpu/gl_context.h" #include "mediapipe/gpu/gl_context_internal.h" +#include "absl/log/absl_check.h" #ifndef EGL_OPENGL_ES3_BIT_KHR #define EGL_OPENGL_ES3_BIT_KHR 0x00000040 @@ -114,7 +115,7 @@ absl::Status GlContext::CreateContextInternal(EGLContext share_context, int gl_version) { - CHECK(gl_version == 2 || gl_version == 3); + ABSL_CHECK(gl_version == 2 || gl_version == 3); const EGLint config_attr[] = { // clang-format off
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gl_context_webgl.cc b/third_party/mediapipe/src/mediapipe/gpu/gl_context_webgl.cc index 1bbb42c8..d087067 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gl_context_webgl.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gl_context_webgl.cc
@@ -24,6 +24,7 @@ #if defined(__EMSCRIPTEN__) #include <emscripten.h> +#include "absl/log/absl_check.h" namespace mediapipe { @@ -48,7 +49,7 @@ absl::Status GlContext::CreateContextInternal( EMSCRIPTEN_WEBGL_CONTEXT_HANDLE external_context, int webgl_version) { - CHECK(webgl_version == 1 || webgl_version == 2); + ABSL_CHECK(webgl_version == 1 || webgl_version == 2); EmscriptenWebGLContextAttributes attrs; emscripten_webgl_init_context_attributes(&attrs);
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gl_scaler_calculator.cc b/third_party/mediapipe/src/mediapipe/gpu/gl_scaler_calculator.cc index fa06c88..14540b5 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gl_scaler_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gl_scaler_calculator.cc
@@ -104,6 +104,7 @@ bool vertical_flip_output_; bool horizontal_flip_output_; FrameScaleMode scale_mode_ = FrameScaleMode::kStretch; + bool use_nearest_neighbor_interpolation_ = false; }; REGISTER_CALCULATOR(GlScalerCalculator); @@ -186,7 +187,8 @@ scale_mode_ = FrameScaleModeFromProto(options.scale_mode(), FrameScaleMode::kStretch); } - + use_nearest_neighbor_interpolation_ = + options.use_nearest_neighbor_interpolation(); if (HasTagOrIndex(cc->InputSidePackets(), "OUTPUT_DIMENSIONS", 1)) { const auto& dimensions = TagOrIndex(cc->InputSidePackets(), "OUTPUT_DIMENSIONS", 1) @@ -297,6 +299,11 @@ glBindTexture(src2.target(), src2.name()); } + if (use_nearest_neighbor_interpolation_) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + MP_RETURN_IF_ERROR(renderer->GlRender( src1.width(), src1.height(), dst.width(), dst.height(), scale_mode_, rotation_, horizontal_flip_output_, vertical_flip_output_,
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gl_scaler_calculator.proto b/third_party/mediapipe/src/mediapipe/gpu/gl_scaler_calculator.proto index 99c0d43..f746a30 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gl_scaler_calculator.proto +++ b/third_party/mediapipe/src/mediapipe/gpu/gl_scaler_calculator.proto
@@ -19,7 +19,7 @@ import "mediapipe/framework/calculator.proto"; import "mediapipe/gpu/scale_mode.proto"; -// Next id: 8. +// Next id: 9. message GlScalerCalculatorOptions { extend CalculatorOptions { optional GlScalerCalculatorOptions ext = 166373014; @@ -39,4 +39,7 @@ // Flip the output texture horizontally. This is applied after rotation. optional bool flip_horizontal = 5; optional ScaleMode.Mode scale_mode = 6; + // Whether to use nearest neighbor interpolation. Default to use linear + // interpolation. + optional bool use_nearest_neighbor_interpolation = 8 [default = false]; }
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gl_texture_buffer.cc b/third_party/mediapipe/src/mediapipe/gpu/gl_texture_buffer.cc index 8c7df3a0..c4f2efb9 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gl_texture_buffer.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gl_texture_buffer.cc
@@ -20,9 +20,9 @@ #include "mediapipe/gpu/gpu_buffer_storage_image_frame.h" #if MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER -#include "absl/log/absl_check.h" #include "mediapipe/gpu/gl_texture_util.h" #include "mediapipe/gpu/gpu_buffer_storage_cv_pixel_buffer.h" +#include "absl/log/absl_check.h" #endif // MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER namespace mediapipe { @@ -128,7 +128,7 @@ if (info.gl_internal_format == GL_RGBA16F && context->GetGlVersion() != GlVersion::kGLES2 && SymbolAvailable(&glTexStorage2D)) { - CHECK(data == nullptr) << "unimplemented"; + ABSL_CHECK(data == nullptr) << "unimplemented"; glTexStorage2D(target_, 1, info.gl_internal_format, width_, height_); } else { glTexImage2D(target_, 0 /* level */, info.gl_internal_format, width_, @@ -150,7 +150,7 @@ // Use the deletion callback to delete the texture on the context // that created it. - CHECK(!deletion_callback_); + ABSL_CHECK(!deletion_callback_); deletion_callback_ = [this, context](std::shared_ptr<GlSyncPoint> sync_token) { ABSL_CHECK_NE(name_, 0); @@ -201,9 +201,9 @@ } void GlTextureBuffer::Updated(std::shared_ptr<GlSyncPoint> prod_token) { - CHECK(!producer_sync_) + ABSL_CHECK(!producer_sync_) << "Updated existing texture which had not been marked for reuse!"; - CHECK(prod_token); + ABSL_CHECK(prod_token); producer_sync_ = std::move(prod_token); const auto& synced_context = producer_sync_->GetContext(); if (synced_context) { @@ -264,11 +264,11 @@ GlTextureView GlTextureBuffer::GetReadView(internal::types<GlTextureView>, int plane) const { auto gl_context = GlContext::GetCurrent(); - CHECK(gl_context); + ABSL_CHECK(gl_context); ABSL_CHECK_EQ(plane, 0); // Note that this method is only supposed to be called by GpuBuffer, which // ensures this condition is satisfied. - DCHECK(!weak_from_this().expired()) + ABSL_DCHECK(!weak_from_this().expired()) << "GlTextureBuffer must be held in shared_ptr to get a GlTextureView"; // Insert wait call to sync with the producer. WaitOnGpu(); @@ -285,11 +285,11 @@ GlTextureView GlTextureBuffer::GetWriteView(internal::types<GlTextureView>, int plane) { auto gl_context = GlContext::GetCurrent(); - CHECK(gl_context); + ABSL_CHECK(gl_context); ABSL_CHECK_EQ(plane, 0); // Note that this method is only supposed to be called by GpuBuffer, which // ensures this condition is satisfied. - DCHECK(!weak_from_this().expired()) + ABSL_DCHECK(!weak_from_this().expired()) << "GlTextureBuffer must be held in shared_ptr to get a GlTextureView"; // Insert wait call to sync with the producer. WaitOnGpu(); @@ -346,7 +346,7 @@ // won't overflow the buffer with glReadPixels, we'd also need to check or // reset several glPixelStore parameters (e.g. what if someone had the // ill-advised idea of setting GL_PACK_SKIP_PIXELS?). - CHECK(view.gl_context()); + ABSL_CHECK(view.gl_context()); GlTextureInfo info = GlTextureInfoForGpuBufferFormat( format, view.plane(), view.gl_context()->GetGlVersion());
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer.cc b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer.cc index 628e860..68d6b7e 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer.cc
@@ -10,6 +10,7 @@ #if MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER #include "mediapipe/objc/util.h" +#include "absl/log/absl_check.h" #endif // MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER namespace mediapipe { @@ -127,10 +128,10 @@ TypeId view_provider_type, bool for_writing) const { auto* chosen_storage = GpuBuffer::GetStorageForView(view_provider_type, for_writing); - CHECK(chosen_storage) << "no view provider found for requested view " + ABSL_CHECK(chosen_storage) << "no view provider found for requested view " << view_provider_type.name() << "; storages available: " << (holder_ ? holder_->DebugString() : "invalid"); - DCHECK(chosen_storage->can_down_cast_to(view_provider_type)); + ABSL_DCHECK(chosen_storage->can_down_cast_to(view_provider_type)); return *chosen_storage; }
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer.h b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer.h index 93eb146..ea75258 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer.h +++ b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer.h
@@ -42,6 +42,7 @@ #include "mediapipe/gpu/gpu_buffer_storage_cv_pixel_buffer.h" #else #include "mediapipe/gpu/gl_texture_buffer.h" +#include "absl/log/absl_check.h" #endif // MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER #endif // MEDIAPIPE_DISABLE_GPU @@ -74,7 +75,7 @@ // GpuBuffers in a portable way from the framework, e.g. using // GpuBufferMultiPool. explicit GpuBuffer(std::shared_ptr<internal::GpuBufferStorage> storage) { - CHECK(storage) << "Cannot construct GpuBuffer with null storage"; + ABSL_CHECK(storage) << "Cannot construct GpuBuffer with null storage"; holder_ = std::make_shared<StorageHolder>(std::move(storage)); }
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_format.cc b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_format.cc index e9f8d0b9..2edcfc5 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_format.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_format.cc
@@ -15,9 +15,9 @@ #include "mediapipe/gpu/gpu_buffer_format.h" #include "absl/container/flat_hash_map.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/deps/no_destructor.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -190,7 +190,7 @@ } auto iter = format_info->find(format); - CHECK(iter != format_info->end()) + ABSL_CHECK(iter != format_info->end()) << "unsupported format: " << static_cast<std::underlying_type_t<decltype(format)>>(format); const auto& planes = iter->second;
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_cv_pixel_buffer.cc b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_cv_pixel_buffer.cc index 642ce23b..5204bf6 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_cv_pixel_buffer.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_cv_pixel_buffer.cc
@@ -2,10 +2,10 @@ #include <memory> -#include "absl/log/absl_check.h" #include "mediapipe/gpu/gl_context.h" #include "mediapipe/gpu/gpu_buffer_storage_image_frame.h" #include "mediapipe/objc/util.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -22,7 +22,7 @@ CVPixelBufferRef buffer; CVReturn err = CreateCVPixelBufferWithoutPool(width, height, cv_format, &buffer); - CHECK(!err) << "Error creating pixel buffer: " << err; + ABSL_CHECK(!err) << "Error creating pixel buffer: " << err; adopt(buffer); } @@ -30,13 +30,13 @@ int plane, GlTextureView::DoneWritingFn done_writing) const { CVReturn err; auto gl_context = GlContext::GetCurrent(); - CHECK(gl_context); + ABSL_CHECK(gl_context); #if TARGET_OS_OSX CVTextureType cv_texture_temp; err = CVOpenGLTextureCacheCreateTextureFromImage( kCFAllocatorDefault, gl_context->cv_texture_cache(), **this, NULL, &cv_texture_temp); - CHECK(cv_texture_temp && !err) + ABSL_CHECK(cv_texture_temp && !err) << "CVOpenGLTextureCacheCreateTextureFromImage failed: " << err; CFHolder<CVTextureType> cv_texture; cv_texture.adopt(cv_texture_temp); @@ -54,7 +54,7 @@ GL_TEXTURE_2D, info.gl_internal_format, width() / info.downscale, height() / info.downscale, info.gl_format, info.gl_type, plane, &cv_texture_temp); - CHECK(cv_texture_temp && !err) + ABSL_CHECK(cv_texture_temp && !err) << "CVOpenGLESTextureCacheCreateTextureFromImage failed: " << err; CFHolder<CVTextureType> cv_texture; cv_texture.adopt(cv_texture_temp); @@ -74,12 +74,12 @@ #if TARGET_IPHONE_SIMULATOR static void ViewDoneWritingSimulatorWorkaround(CVPixelBufferRef pixel_buffer, const GlTextureView& view) { - CHECK(pixel_buffer); + ABSL_CHECK(pixel_buffer); auto ctx = GlContext::GetCurrent().get(); if (!ctx) ctx = view.gl_context(); ctx->Run([pixel_buffer, &view, ctx] { CVReturn err = CVPixelBufferLockBaseAddress(pixel_buffer, 0); - CHECK(err == kCVReturnSuccess) + ABSL_CHECK(err == kCVReturnSuccess) << "CVPixelBufferLockBaseAddress failed: " << err; OSType pixel_format = CVPixelBufferGetPixelFormatType(pixel_buffer); size_t bytes_per_row = CVPixelBufferGetBytesPerRow(pixel_buffer); @@ -117,7 +117,7 @@ LOG(ERROR) << "unsupported pixel format: " << pixel_format; } err = CVPixelBufferUnlockBaseAddress(pixel_buffer, 0); - CHECK(err == kCVReturnSuccess) + ABSL_CHECK(err == kCVReturnSuccess) << "CVPixelBufferUnlockBaseAddress failed: " << err; }); } @@ -150,7 +150,7 @@ std::shared_ptr<GpuBufferStorageImageFrame> frame) { auto status_or_buffer = CreateCVPixelBufferForImageFrame(frame->image_frame()); - CHECK(status_or_buffer.ok()); + ABSL_CHECK(status_or_buffer.ok()); return std::make_shared<GpuBufferStorageCvPixelBuffer>( std::move(status_or_buffer).value()); }
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_image_frame.cc b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_image_frame.cc index 316c6cc4..3f89df9 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_image_frame.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_image_frame.cc
@@ -20,6 +20,7 @@ #include "mediapipe/framework/formats/frame_buffer.h" #include "mediapipe/framework/formats/image_frame.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -43,7 +44,7 @@ std::shared_ptr<ImageFrame> image_frame) { FrameBuffer::Format format = FrameBufferFormatForImageFrameFormat(image_frame->Format()); - CHECK(format != FrameBuffer::Format::kUNKNOWN) + ABSL_CHECK(format != FrameBuffer::Format::kUNKNOWN) << "Invalid format. Only SRGB, SRGBA and GRAY8 are supported."; const FrameBuffer::Dimension dimension{/*width=*/image_frame->Width(), /*height=*/image_frame->Height()};
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_yuv_image.cc b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_yuv_image.cc index 41905de..40d162e 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_yuv_image.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gpu_buffer_storage_yuv_image.cc
@@ -27,6 +27,7 @@ #include "mediapipe/framework/formats/yuv_image.h" #include "mediapipe/gpu/gpu_buffer_format.h" #include "mediapipe/util/frame_buffer/frame_buffer_util.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -87,7 +88,7 @@ FrameBuffer::Dimension dimension{/*width=*/yuv_image->width(), /*height=*/yuv_image->height()}; std::vector<FrameBuffer::Plane> planes; - CHECK(yuv_image->mutable_data(0) != nullptr && yuv_image->stride(0) > 0) + ABSL_CHECK(yuv_image->mutable_data(0) != nullptr && yuv_image->stride(0) > 0) << "Invalid YuvImage. Expected plane at index 0 to be non-null and have " "stride > 0."; planes.emplace_back( @@ -97,7 +98,7 @@ switch (format) { case FrameBuffer::Format::kNV12: case FrameBuffer::Format::kNV21: { - CHECK(yuv_image->mutable_data(1) != nullptr && yuv_image->stride(1) > 0) + ABSL_CHECK(yuv_image->mutable_data(1) != nullptr && yuv_image->stride(1) > 0) << "Invalid YuvImage. Expected plane at index 1 to be non-null and " "have stride > 0."; planes.emplace_back( @@ -108,7 +109,7 @@ } case FrameBuffer::Format::kYV12: case FrameBuffer::Format::kYV21: { - CHECK(yuv_image->mutable_data(1) != nullptr && yuv_image->stride(1) > 0 && + ABSL_CHECK(yuv_image->mutable_data(1) != nullptr && yuv_image->stride(1) > 0 && yuv_image->mutable_data(2) != nullptr && yuv_image->stride(2) > 0) << "Invalid YuvImage. Expected planes at indices 1 and 2 to be " "non-null and have stride > 0."; @@ -156,7 +157,7 @@ GpuBufferStorageYuvImage::GpuBufferStorageYuvImage( std::shared_ptr<YUVImage> yuv_image) { - CHECK(GpuBufferFormatForFourCC(yuv_image->fourcc()) != + ABSL_CHECK(GpuBufferFormatForFourCC(yuv_image->fourcc()) != GpuBufferFormat::kUnknown) << "Invalid format. Only FOURCC_NV12, FOURCC_NV21, FOURCC_YV12 and " "FOURCC_I420 are supported.";
diff --git a/third_party/mediapipe/src/mediapipe/gpu/gpu_shared_data_internal.cc b/third_party/mediapipe/src/mediapipe/gpu/gpu_shared_data_internal.cc index 1098c82..c4a5a18 100644 --- a/third_party/mediapipe/src/mediapipe/gpu/gpu_shared_data_internal.cc +++ b/third_party/mediapipe/src/mediapipe/gpu/gpu_shared_data_internal.cc
@@ -23,6 +23,7 @@ #if __APPLE__ #include "mediapipe/gpu/metal_shared_resources.h" +#include "absl/log/absl_check.h" #endif // __APPLE__ namespace mediapipe { @@ -120,7 +121,7 @@ ABSL_CONST_INIT extern const GraphService<GpuResources> kGpuService; absl::Status GpuResources::PrepareGpuNode(CalculatorNode* node) { - CHECK(node->Contract().ServiceRequests().contains(kGpuService.key)); + ABSL_CHECK(node->Contract().ServiceRequests().contains(kGpuService.key)); std::string node_id = node->GetCalculatorState().NodeName(); std::string node_type = node->GetCalculatorState().CalculatorType(); std::string context_key;
diff --git a/third_party/mediapipe/src/mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.cc b/third_party/mediapipe/src/mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.cc index a92020ff..8fa3c4b 100644 --- a/third_party/mediapipe/src/mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.cc
@@ -27,6 +27,7 @@ #include "mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.pb.h" #include "mediapipe/graphs/object_detection_3d/calculators/model_matrix.pb.h" #include "mediapipe/modules/objectron/calculators/camera_parameters.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -506,8 +507,8 @@ void GlAnimationOverlayCalculator::ComputeAspectRatioAndFovFromCameraParameters( const CameraParametersProto &camera_parameters, float *aspect_ratio, float *vertical_fov_degrees) { - CHECK(aspect_ratio != nullptr); - CHECK(vertical_fov_degrees != nullptr); + ABSL_CHECK(aspect_ratio != nullptr); + ABSL_CHECK(vertical_fov_degrees != nullptr); *aspect_ratio = camera_parameters.portrait_width() / camera_parameters.portrait_height(); *vertical_fov_degrees = @@ -608,7 +609,7 @@ current_model_matrices->clear(); for (int i = 0; i < model_matrices.model_matrix_size(); ++i) { const auto &model_matrix = model_matrices.model_matrix(i); - CHECK(model_matrix.matrix_entries_size() == kNumMatrixEntries) + ABSL_CHECK(model_matrix.matrix_entries_size() == kNumMatrixEntries) << "Invalid Model Matrix"; current_model_matrices->emplace_back(); ModelMatrix &new_matrix = current_model_matrices->back();
diff --git a/third_party/mediapipe/src/mediapipe/model_maker/requirements.txt b/third_party/mediapipe/src/mediapipe/model_maker/requirements.txt index 5c78dc58..a1c975c1 100644 --- a/third_party/mediapipe/src/mediapipe/model_maker/requirements.txt +++ b/third_party/mediapipe/src/mediapipe/model_maker/requirements.txt
@@ -3,6 +3,7 @@ numpy opencv-python tensorflow>=2.10 +tensorflow-addons tensorflow-datasets tensorflow-hub -tf-models-official==2.11.6 +tf-models-official>=2.13.1
diff --git a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/box.cc b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/box.cc index 9b3e434..cc9efca7 100644 --- a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/box.cc +++ b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/box.cc
@@ -15,8 +15,8 @@ #include "mediapipe/modules/objectron/calculators/box.h" #include "Eigen/Core" -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/box_util.cc b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/box_util.cc index 0663b5b..e2434fcf 100644 --- a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/box_util.cc +++ b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/box_util.cc
@@ -20,11 +20,12 @@ #include "mediapipe/framework/port/opencv_core_inc.h" #include "mediapipe/framework/port/opencv_imgproc_inc.h" #include "mediapipe/util/tracking/box_tracker.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { void ComputeBoundingRect(const std::vector<cv::Point2f>& points, mediapipe::TimedBoxProto* box) { - CHECK(box != nullptr); + ABSL_CHECK(box != nullptr); float top = 1.0f; float bottom = 0.0f; float left = 1.0f;
diff --git a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/decoder.cc b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/decoder.cc index ba6e98a..bd8a42e 100644 --- a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/decoder.cc +++ b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/decoder.cc
@@ -19,7 +19,6 @@ #include "Eigen/Core" #include "Eigen/Dense" -#include "absl/log/absl_check.h" #include "absl/status/status.h" #include "mediapipe/framework/port/canonical_errors.h" #include "mediapipe/framework/port/logging.h" @@ -29,6 +28,7 @@ #include "mediapipe/modules/objectron/calculators/box.h" #include "mediapipe/modules/objectron/calculators/epnp.h" #include "mediapipe/modules/objectron/calculators/types.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -202,7 +202,7 @@ absl::Status Decoder::Lift2DTo3D( const Eigen::Matrix<float, 4, 4, Eigen::RowMajor>& projection_matrix, bool portrait, FrameAnnotation* estimated_box) const { - CHECK(estimated_box != nullptr); + ABSL_CHECK(estimated_box != nullptr); for (auto& annotation : *estimated_box->mutable_annotations()) { ABSL_CHECK_EQ(kNumKeypoints, annotation.keypoints_size());
diff --git a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/epnp.cc b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/epnp.cc index 03b78c72..1fd97f1d 100644 --- a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/epnp.cc +++ b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/epnp.cc
@@ -13,7 +13,6 @@ // limitations under the License. #include "mediapipe/modules/objectron/calculators/epnp.h" - #include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/frame_annotation_tracker.cc b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/frame_annotation_tracker.cc index 1685a4f6..9c814834 100644 --- a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/frame_annotation_tracker.cc +++ b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/frame_annotation_tracker.cc
@@ -19,6 +19,7 @@ #include "mediapipe/modules/objectron/calculators/annotation_data.pb.h" #include "mediapipe/modules/objectron/calculators/box_util.h" #include "mediapipe/util/tracking/box_tracker.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -35,7 +36,7 @@ FrameAnnotation FrameAnnotationTracker::ConsolidateTrackingResult( const TimedBoxProtoList& tracked_boxes, absl::flat_hash_set<int>* cancel_object_ids) { - CHECK(cancel_object_ids != nullptr); + ABSL_CHECK(cancel_object_ids != nullptr); FrameAnnotation frame_annotation; std::vector<int64_t> keys_to_be_deleted; for (const auto& detected_obj : detected_objects_) {
diff --git a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/model.cc b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/model.cc index d6fe9ed..80e43c2d 100644 --- a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/model.cc +++ b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/model.cc
@@ -14,8 +14,8 @@ #include "mediapipe/modules/objectron/calculators/model.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tensor_util.cc b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tensor_util.cc index e482565..d72ff83 100644 --- a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tensor_util.cc +++ b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tensor_util.cc
@@ -14,16 +14,15 @@ #include "mediapipe/modules/objectron/calculators/tensor_util.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { cv::Mat ConvertTfliteTensorToCvMat(const TfLiteTensor& tensor) { // Check tensor is BxCxWxH (size = 4) and the batch size is one(data[0] = 1) - CHECK(tensor.dims->size == 4 && tensor.dims->data[0] == 1); - ABSL_CHECK_EQ(kTfLiteFloat32, tensor.type) - << "tflite_tensor type is not float"; + ABSL_CHECK(tensor.dims->size == 4 && tensor.dims->data[0] == 1); + ABSL_CHECK_EQ(kTfLiteFloat32, tensor.type) << "tflite_tensor type is not float"; const size_t num_output_channels = tensor.dims->data[3]; const int dims = 2; @@ -34,9 +33,9 @@ cv::Mat ConvertTensorToCvMat(const mediapipe::Tensor& tensor) { // Check tensor is BxCxWxH (size = 4) and the batch size is one(data[0] = 1) - CHECK(tensor.shape().dims.size() == 4 && tensor.shape().dims[0] == 1); - ABSL_CHECK_EQ( - mediapipe::Tensor::ElementType::kFloat32 == tensor.element_type(), true) + ABSL_CHECK(tensor.shape().dims.size() == 4 && tensor.shape().dims[0] == 1); + ABSL_CHECK_EQ(mediapipe::Tensor::ElementType::kFloat32 == tensor.element_type(), + true) << "tensor type is not float"; const size_t num_output_channels = tensor.shape().dims[3];
diff --git a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tensors_to_objects_calculator.cc b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tensors_to_objects_calculator.cc index d0fc612..9a97fff 100644 --- a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tensors_to_objects_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tensors_to_objects_calculator.cc
@@ -17,7 +17,6 @@ #include <vector> #include "Eigen/Dense" -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/strings/str_format.h" #include "absl/types/span.h" @@ -31,6 +30,7 @@ #include "mediapipe/modules/objectron/calculators/decoder.h" #include "mediapipe/modules/objectron/calculators/tensor_util.h" #include "mediapipe/modules/objectron/calculators/tensors_to_objects_calculator.pb.h" +#include "absl/log/absl_check.h" namespace { constexpr char kInputStreamTag[] = "TENSORS";
diff --git a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tflite_tensors_to_objects_calculator.cc b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tflite_tensors_to_objects_calculator.cc index 98aeb736..40d74d0 100644 --- a/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tflite_tensors_to_objects_calculator.cc +++ b/third_party/mediapipe/src/mediapipe/modules/objectron/calculators/tflite_tensors_to_objects_calculator.cc
@@ -17,7 +17,6 @@ #include <vector> #include "Eigen/Dense" -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "absl/strings/str_format.h" #include "absl/types/span.h" @@ -31,6 +30,7 @@ #include "mediapipe/modules/objectron/calculators/tensor_util.h" #include "mediapipe/modules/objectron/calculators/tflite_tensors_to_objects_calculator.pb.h" #include "tensorflow/lite/interpreter.h" +#include "absl/log/absl_check.h" namespace { constexpr char kInputStreamTag[] = "TENSORS";
diff --git a/third_party/mediapipe/src/mediapipe/tasks/c/components/containers/BUILD b/third_party/mediapipe/src/mediapipe/tasks/c/components/containers/BUILD new file mode 100644 index 0000000..4d1f190 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/tasks/c/components/containers/BUILD
@@ -0,0 +1,29 @@ +# TODO: describe this package. + +# Copyright 2022 The MediaPipe Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//mediapipe/tasks:internal"]) + +licenses(["notice"]) + +cc_library( + name = "category", + hdrs = ["category.h"], +) + +cc_library( + name = "classification_result", + hdrs = ["classification_result.h"], +)
diff --git a/third_party/mediapipe/src/mediapipe/tasks/c/components/containers/category.h b/third_party/mediapipe/src/mediapipe/tasks/c/components/containers/category.h new file mode 100644 index 0000000..565dd65f --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/tasks/c/components/containers/category.h
@@ -0,0 +1,42 @@ +/* Copyright 2023 The MediaPipe Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CATEGORY_H_ +#define MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CATEGORY_H_ + +// Defines a single classification result. +// +// The label maps packed into the TFLite Model Metadata [1] are used to populate +// the 'category_name' and 'display_name' fields. +// +// [1]: https://www.tensorflow.org/lite/convert/metadata +struct Category { + // The index of the category in the classification model output. + int index; + + // The score for this category, e.g. (but not necessarily) a probability in + // [0,1]. + float score; + + // The optional ID for the category, read from the label map packed in the + // TFLite Model Metadata if present. Not necessarily human-readable. + char* category_name; + + // The optional human-readable name for the category, read from the label map + // packed in the TFLite Model Metadata if present. + char* display_name; +}; + +#endif // MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CATEGORY_H_
diff --git a/third_party/mediapipe/src/mediapipe/tasks/c/components/containers/classification_result.h b/third_party/mediapipe/src/mediapipe/tasks/c/components/containers/classification_result.h new file mode 100644 index 0000000..540ab44 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/tasks/c/components/containers/classification_result.h
@@ -0,0 +1,60 @@ +/* Copyright 2023 The MediaPipe Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CLASSIFICATION_RESULT_H_ +#define MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CLASSIFICATION_RESULT_H_ + +#include <stdbool.h> +#include <stdint.h> + +// Defines classification results for a given classifier head. +struct Classifications { + // The array of predicted categories, usually sorted by descending scores, + // e.g. from high to low probability. + struct Category* categories; + // The number of elements in the categories array. + uint32_t categories_count; + + // The index of the classifier head (i.e. output tensor) these categories + // refer to. This is useful for multi-head models. + int head_index; + + // The optional name of the classifier head, as provided in the TFLite Model + // Metadata [1] if present. This is useful for multi-head models. + // + // [1]: https://www.tensorflow.org/lite/convert/metadata + char* head_name; +}; + +// Defines classification results of a model. +struct ClassificationResult { + // The classification results for each head of the model. + struct Classifications* classifications; + // The number of classifications in the classifications array. + uint32_t classifications_count; + + // The optional timestamp (in milliseconds) of the start of the chunk of data + // corresponding to these results. + // + // This is only used for classification on time series (e.g. audio + // classification). In these use cases, the amount of data to process might + // exceed the maximum size that the model can process: to solve this, the + // input data is split into multiple chunks starting at different timestamps. + int64_t timestamp_ms; + // Specifies whether the timestamp contains a valid value. + bool has_timestamp_ms; +}; + +#endif // MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CLASSIFICATION_RESULT_H_
diff --git a/third_party/mediapipe/src/mediapipe/tasks/c/components/processors/BUILD b/third_party/mediapipe/src/mediapipe/tasks/c/components/processors/BUILD new file mode 100644 index 0000000..24d3a181 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/tasks/c/components/processors/BUILD
@@ -0,0 +1,22 @@ +# Copyright 2023 The MediaPipe Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//mediapipe/tasks:internal"]) + +licenses(["notice"]) + +cc_library( + name = "classifier_options", + hdrs = ["classifier_options.h"], +)
diff --git a/third_party/mediapipe/src/mediapipe/tasks/c/components/processors/classifier_options.h b/third_party/mediapipe/src/mediapipe/tasks/c/components/processors/classifier_options.h new file mode 100644 index 0000000..4cce2ce6 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/tasks/c/components/processors/classifier_options.h
@@ -0,0 +1,51 @@ +/* Copyright 2023 The MediaPipe Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef MEDIAPIPE_TASKS_C_COMPONENTS_PROCESSORS_CLASSIFIER_OPTIONS_H_ +#define MEDIAPIPE_TASKS_C_COMPONENTS_PROCESSORS_CLASSIFIER_OPTIONS_H_ + +#include <stdint.h> + +// Classifier options for MediaPipe C classification Tasks. +struct ClassifierOptions { + // The locale to use for display names specified through the TFLite Model + // Metadata, if any. Defaults to English. + char* display_names_locale; + + // The maximum number of top-scored classification results to return. If < 0, + // all available results will be returned. If 0, an invalid argument error is + // returned. + int max_results; + + // Score threshold to override the one provided in the model metadata (if + // any). Results below this value are rejected. + float score_threshold; + + // The allowlist of category names. If non-empty, detection results whose + // category name is not in this set will be filtered out. Duplicate or unknown + // category names are ignored. Mutually exclusive with category_denylist. + char** category_allowlist; + // The number of elements in the category allowlist. + uint32_t category_allowlist_count; + + // The denylist of category names. If non-empty, detection results whose + // category name is in this set will be filtered out. Duplicate or unknown + // category names are ignored. Mutually exclusive with category_allowlist. + char** category_denylist = {}; + // The number of elements in the category denylist. + uint32_t category_denylist_count; +}; + +#endif // MEDIAPIPE_TASKS_C_COMPONENTS_PROCESSORS_CLASSIFIER_OPTIONS_H_
diff --git a/third_party/mediapipe/src/mediapipe/tasks/c/core/BUILD b/third_party/mediapipe/src/mediapipe/tasks/c/core/BUILD new file mode 100644 index 0000000..60d1085 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/tasks/c/core/BUILD
@@ -0,0 +1,22 @@ +# Copyright 2023 The MediaPipe Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//mediapipe/tasks:internal"]) + +licenses(["notice"]) + +cc_library( + name = "base_options", + hdrs = ["base_options.h"], +)
diff --git a/third_party/mediapipe/src/mediapipe/tasks/c/core/base_options.h b/third_party/mediapipe/src/mediapipe/tasks/c/core/base_options.h new file mode 100644 index 0000000..f5f6b03 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/tasks/c/core/base_options.h
@@ -0,0 +1,28 @@ +/* Copyright 2023 The MediaPipe Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef MEDIAPIPE_TASKS_C_CORE_BASE_OPTIONS_H_ +#define MEDIAPIPE_TASKS_C_CORE_BASE_OPTIONS_H_ + +// Base options for MediaPipe C Tasks. +struct BaseOptions { + // The model asset file contents as a string. + char* model_asset_buffer; + + // The path to the model asset to open and mmap in memory. + char* model_asset_path; +}; + +#endif // MEDIAPIPE_TASKS_C_CORE_BASE_OPTIONS_H_
diff --git a/third_party/mediapipe/src/mediapipe/tasks/c/text/text_classifier/BUILD b/third_party/mediapipe/src/mediapipe/tasks/c/text/text_classifier/BUILD new file mode 100644 index 0000000..0402689 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/tasks/c/text/text_classifier/BUILD
@@ -0,0 +1,28 @@ +# Copyright 2023 The MediaPipe Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//mediapipe/tasks:internal"]) + +licenses(["notice"]) + +cc_library( + name = "text_classifier", + hdrs = ["text_classifier.h"], + visibility = ["//visibility:public"], + deps = [ + "//mediapipe/tasks/c/components/containers:classification_result", + "//mediapipe/tasks/c/components/processors:classifier_options", + "//mediapipe/tasks/c/core:base_options", + ], +)
diff --git a/third_party/mediapipe/src/mediapipe/tasks/c/text/text_classifier/text_classifier.h b/third_party/mediapipe/src/mediapipe/tasks/c/text/text_classifier/text_classifier.h new file mode 100644 index 0000000..7439644 --- /dev/null +++ b/third_party/mediapipe/src/mediapipe/tasks/c/text/text_classifier/text_classifier.h
@@ -0,0 +1,46 @@ +/* Copyright 2023 The MediaPipe Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef MEDIAPIPE_TASKS_C_TEXT_TEXT_CLASSIFIER_TEXT_CLASSIFIER_H_ +#define MEDIAPIPE_TASKS_C_TEXT_TEXT_CLASSIFIER_TEXT_CLASSIFIER_H_ + +#include "mediapipe/tasks/c/components/containers/classification_result.h" +#include "mediapipe/tasks/c/components/processors/classifier_options.h" +#include "mediapipe/tasks/c/core/base_options.h" + +typedef ClassificationResult TextClassifierResult; + +// The options for configuring a MediaPipe text classifier task. +struct TextClassifierOptions { + // Base options for configuring MediaPipe Tasks, such as specifying the model + // file with metadata, accelerator options, op resolver, etc. + struct BaseOptions base_options; + + // Options for configuring the classifier behavior, such as score threshold, + // number of results, etc. + struct ClassifierOptions classifier_options; +}; + +// Creates a TextClassifier from the provided `options`. +void* text_classifier_create(struct TextClassifierOptions options); + +// Performs classification on the input `text`. +TextClassifierResult text_classifier_classify(void* classifier, + char* utf8_text); + +// Shuts down the TextClassifier when all the work is done. Frees all memory. +void text_classifier_close(void* classifier); + +#endif // MEDIAPIPE_TASKS_C_TEXT_TEXT_CLASSIFIER_TEXT_CLASSIFIER_H_
diff --git a/third_party/mediapipe/src/mediapipe/tasks/cc/text/custom_ops/ragged/ragged_tensor_to_tensor_tflite.cc b/third_party/mediapipe/src/mediapipe/tasks/cc/text/custom_ops/ragged/ragged_tensor_to_tensor_tflite.cc index 3f0ed01..62af36f 100644 --- a/third_party/mediapipe/src/mediapipe/tasks/cc/text/custom_ops/ragged/ragged_tensor_to_tensor_tflite.cc +++ b/third_party/mediapipe/src/mediapipe/tasks/cc/text/custom_ops/ragged/ragged_tensor_to_tensor_tflite.cc
@@ -18,7 +18,6 @@ #include <cstdint> #include <memory> -#include "absl/log/absl_check.h" #include "flatbuffers/flexbuffers.h" #include "tensorflow/core/util/ragged_to_dense_util_common.h" #include "tensorflow/lite/c/common.h" @@ -29,6 +28,7 @@ #include "tensorflow/lite/kernels/internal/types.h" #include "tensorflow/lite/kernels/kernel_util.h" #include "tensorflow/lite/model.h" +#include "absl/log/absl_check.h" namespace mediapipe::tflite_operations { namespace ragged::ragged_tensor_to_tensor {
diff --git a/third_party/mediapipe/src/mediapipe/tasks/cc/text/text_embedder/text_embedder_graph.cc b/third_party/mediapipe/src/mediapipe/tasks/cc/text/text_embedder/text_embedder_graph.cc index 9c812e9f..87c9f89 100644 --- a/third_party/mediapipe/src/mediapipe/tasks/cc/text/text_embedder/text_embedder_graph.cc +++ b/third_party/mediapipe/src/mediapipe/tasks/cc/text/text_embedder/text_embedder_graph.cc
@@ -33,6 +33,7 @@ #include "mediapipe/tasks/cc/core/proto/model_resources_calculator.pb.h" #include "mediapipe/tasks/cc/text/text_embedder/proto/text_embedder_graph_options.pb.h" #include "mediapipe/tasks/cc/text/utils/text_model_utils.h" +#include "absl/log/absl_check.h" namespace mediapipe::tasks::text::text_embedder { namespace { @@ -86,7 +87,7 @@ public: absl::StatusOr<CalculatorGraphConfig> GetConfig( SubgraphContext* sc) override { - CHECK(sc != nullptr); + ABSL_CHECK(sc != nullptr); ASSIGN_OR_RETURN(const ModelResources* model_resources, CreateModelResources<proto::TextEmbedderGraphOptions>(sc)); Graph graph;
diff --git a/third_party/mediapipe/src/mediapipe/util/android/asset_manager_util.cc b/third_party/mediapipe/src/mediapipe/util/android/asset_manager_util.cc index 8b5803d..663d7db5 100644 --- a/third_party/mediapipe/src/mediapipe/util/android/asset_manager_util.cc +++ b/third_party/mediapipe/src/mediapipe/util/android/asset_manager_util.cc
@@ -21,6 +21,7 @@ #include "mediapipe/java/com/google/mediapipe/framework/jni/jni_util.h" #include "mediapipe/util/android/file/base/file.h" #include "mediapipe/util/android/file/base/filesystem.h" +#include "absl/log/absl_check.h" namespace { @@ -132,7 +133,7 @@ } bool AssetManager::ReadFile(const std::string& filename, std::string* output) { - CHECK(output); + ABSL_CHECK(output); if (!asset_manager_) { LOG(ERROR) << "Asset manager was not initialized from JNI"; return false;
diff --git a/third_party/mediapipe/src/mediapipe/util/annotation_renderer.cc b/third_party/mediapipe/src/mediapipe/util/annotation_renderer.cc index e1aa484..a504541f 100644 --- a/third_party/mediapipe/src/mediapipe/util/annotation_renderer.cc +++ b/third_party/mediapipe/src/mediapipe/util/annotation_renderer.cc
@@ -19,11 +19,11 @@ #include <algorithm> #include <cmath> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/vector.h" #include "mediapipe/util/color.pb.h" #include "mediapipe/util/render_data.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace { @@ -48,8 +48,8 @@ bool NormalizedtoPixelCoordinates(double normalized_x, double normalized_y, int image_width, int image_height, int* x_px, int* y_px) { - CHECK(x_px != nullptr); - CHECK(y_px != nullptr); + ABSL_CHECK(x_px != nullptr); + ABSL_CHECK(y_px != nullptr); ABSL_CHECK_GT(image_width, 0); ABSL_CHECK_GT(image_height, 0); @@ -148,10 +148,10 @@ int bottom = -1; const auto& rectangle = annotation.rectangle(); if (rectangle.normalized()) { - CHECK(NormalizedtoPixelCoordinates(rectangle.left(), rectangle.top(), + ABSL_CHECK(NormalizedtoPixelCoordinates(rectangle.left(), rectangle.top(), image_width_, image_height_, &left, &top)); - CHECK(NormalizedtoPixelCoordinates(rectangle.right(), rectangle.bottom(), + ABSL_CHECK(NormalizedtoPixelCoordinates(rectangle.right(), rectangle.bottom(), image_width_, image_height_, &right, &bottom)); } else { @@ -200,10 +200,10 @@ int bottom = -1; const auto& rectangle = annotation.filled_rectangle().rectangle(); if (rectangle.normalized()) { - CHECK(NormalizedtoPixelCoordinates(rectangle.left(), rectangle.top(), + ABSL_CHECK(NormalizedtoPixelCoordinates(rectangle.left(), rectangle.top(), image_width_, image_height_, &left, &top)); - CHECK(NormalizedtoPixelCoordinates(rectangle.right(), rectangle.bottom(), + ABSL_CHECK(NormalizedtoPixelCoordinates(rectangle.right(), rectangle.bottom(), image_width_, image_height_, &right, &bottom)); } else { @@ -240,10 +240,10 @@ int bottom = -1; const auto& rectangle = annotation.rounded_rectangle().rectangle(); if (rectangle.normalized()) { - CHECK(NormalizedtoPixelCoordinates(rectangle.left(), rectangle.top(), + ABSL_CHECK(NormalizedtoPixelCoordinates(rectangle.left(), rectangle.top(), image_width_, image_height_, &left, &top)); - CHECK(NormalizedtoPixelCoordinates(rectangle.right(), rectangle.bottom(), + ABSL_CHECK(NormalizedtoPixelCoordinates(rectangle.right(), rectangle.bottom(), image_width_, image_height_, &right, &bottom)); } else { @@ -273,10 +273,10 @@ const auto& rectangle = annotation.filled_rounded_rectangle().rounded_rectangle().rectangle(); if (rectangle.normalized()) { - CHECK(NormalizedtoPixelCoordinates(rectangle.left(), rectangle.top(), + ABSL_CHECK(NormalizedtoPixelCoordinates(rectangle.left(), rectangle.top(), image_width_, image_height_, &left, &top)); - CHECK(NormalizedtoPixelCoordinates(rectangle.right(), rectangle.bottom(), + ABSL_CHECK(NormalizedtoPixelCoordinates(rectangle.right(), rectangle.bottom(), image_width_, image_height_, &right, &bottom)); } else { @@ -345,10 +345,10 @@ int bottom = -1; const auto& enclosing_rectangle = annotation.oval().rectangle(); if (enclosing_rectangle.normalized()) { - CHECK(NormalizedtoPixelCoordinates(enclosing_rectangle.left(), + ABSL_CHECK(NormalizedtoPixelCoordinates(enclosing_rectangle.left(), enclosing_rectangle.top(), image_width_, image_height_, &left, &top)); - CHECK(NormalizedtoPixelCoordinates( + ABSL_CHECK(NormalizedtoPixelCoordinates( enclosing_rectangle.right(), enclosing_rectangle.bottom(), image_width_, image_height_, &right, &bottom)); } else { @@ -374,10 +374,10 @@ int bottom = -1; const auto& enclosing_rectangle = annotation.filled_oval().oval().rectangle(); if (enclosing_rectangle.normalized()) { - CHECK(NormalizedtoPixelCoordinates(enclosing_rectangle.left(), + ABSL_CHECK(NormalizedtoPixelCoordinates(enclosing_rectangle.left(), enclosing_rectangle.top(), image_width_, image_height_, &left, &top)); - CHECK(NormalizedtoPixelCoordinates( + ABSL_CHECK(NormalizedtoPixelCoordinates( enclosing_rectangle.right(), enclosing_rectangle.bottom(), image_width_, image_height_, &right, &bottom)); } else { @@ -403,10 +403,10 @@ const auto& arrow = annotation.arrow(); if (arrow.normalized()) { - CHECK(NormalizedtoPixelCoordinates(arrow.x_start(), arrow.y_start(), + ABSL_CHECK(NormalizedtoPixelCoordinates(arrow.x_start(), arrow.y_start(), image_width_, image_height_, &x_start, &y_start)); - CHECK(NormalizedtoPixelCoordinates(arrow.x_end(), arrow.y_end(), + ABSL_CHECK(NormalizedtoPixelCoordinates(arrow.x_end(), arrow.y_end(), image_width_, image_height_, &x_end, &y_end)); } else { @@ -454,7 +454,7 @@ int x = -1; int y = -1; if (point.normalized()) { - CHECK(NormalizedtoPixelCoordinates(point.x(), point.y(), image_width_, + ABSL_CHECK(NormalizedtoPixelCoordinates(point.x(), point.y(), image_width_, image_height_, &x, &y)); } else { x = static_cast<int>(point.x() * scale_factor_); @@ -482,10 +482,10 @@ const auto& line = annotation.line(); if (line.normalized()) { - CHECK(NormalizedtoPixelCoordinates(line.x_start(), line.y_start(), + ABSL_CHECK(NormalizedtoPixelCoordinates(line.x_start(), line.y_start(), image_width_, image_height_, &x_start, &y_start)); - CHECK(NormalizedtoPixelCoordinates(line.x_end(), line.y_end(), image_width_, + ABSL_CHECK(NormalizedtoPixelCoordinates(line.x_end(), line.y_end(), image_width_, image_height_, &x_end, &y_end)); } else { x_start = static_cast<int>(line.x_start() * scale_factor_); @@ -510,10 +510,10 @@ const auto& line = annotation.gradient_line(); if (line.normalized()) { - CHECK(NormalizedtoPixelCoordinates(line.x_start(), line.y_start(), + ABSL_CHECK(NormalizedtoPixelCoordinates(line.x_start(), line.y_start(), image_width_, image_height_, &x_start, &y_start)); - CHECK(NormalizedtoPixelCoordinates(line.x_end(), line.y_end(), image_width_, + ABSL_CHECK(NormalizedtoPixelCoordinates(line.x_end(), line.y_end(), image_width_, image_height_, &x_end, &y_end)); } else { x_start = static_cast<int>(line.x_start() * scale_factor_); @@ -538,7 +538,7 @@ const auto& text = annotation.text(); if (text.normalized()) { - CHECK(NormalizedtoPixelCoordinates(text.left(), text.baseline(), + ABSL_CHECK(NormalizedtoPixelCoordinates(text.left(), text.baseline(), image_width_, image_height_, &left, &baseline)); font_size = static_cast<int>(round(text.font_height() * image_height_));
diff --git a/third_party/mediapipe/src/mediapipe/util/audio_decoder.cc b/third_party/mediapipe/src/mediapipe/util/audio_decoder.cc index 569e8015..2499c00 100644 --- a/third_party/mediapipe/src/mediapipe/util/audio_decoder.cc +++ b/third_party/mediapipe/src/mediapipe/util/audio_decoder.cc
@@ -39,6 +39,7 @@ #include "libavutil/avutil.h" #include "libavutil/mem.h" #include "libavutil/samplefmt.h" +#include "absl/log/absl_check.h" } ABSL_FLAG(int64_t, media_decoder_allowed_audio_gap_merge, 5, @@ -227,8 +228,8 @@ bool BasePacketProcessor::HasData() { return !buffer_.empty(); } absl::Status BasePacketProcessor::GetData(Packet* packet) { - CHECK(packet); - CHECK(!buffer_.empty()); + ABSL_CHECK(packet); + ABSL_CHECK(!buffer_.empty()); *packet = buffer_.front(); buffer_.pop_front(); @@ -335,7 +336,7 @@ AudioPacketProcessor::AudioPacketProcessor(const AudioStreamOptions& options) : sample_time_base_{0, 0}, options_(options) { - DCHECK(absl::little_endian::IsLittleEndian()); + ABSL_DCHECK(absl::little_endian::IsLittleEndian()); } absl::Status AudioPacketProcessor::Open(int id, AVStream* stream) { @@ -349,7 +350,7 @@ if (avcodec_open2(avcodec_ctx_, avcodec_, &avcodec_opts_) < 0) { return UnknownError("avcodec_open() failed."); } - CHECK(avcodec_ctx_->codec); + ABSL_CHECK(avcodec_ctx_->codec); source_time_base_ = stream->time_base; source_frame_rate_ = stream->r_frame_rate; @@ -411,7 +412,7 @@ } absl::Status AudioPacketProcessor::ProcessPacket(AVPacket* packet) { - CHECK(packet); + ABSL_CHECK(packet); if (flushed_) { return UnknownError( "ProcessPacket was called, but AudioPacketProcessor is already " @@ -575,7 +576,7 @@ } absl::Status AudioPacketProcessor::FillHeader(TimeSeriesHeader* header) const { - CHECK(header); + ABSL_CHECK(header); header->set_sample_rate(sample_rate_); header->set_num_channels(num_channels_); return absl::OkStatus(); @@ -655,13 +656,13 @@ MP_RETURN_IF_ERROR(processor->Open(stream_id, stream)); audio_processor_.emplace(stream_id, std::move(processor)); - CHECK(InsertIfNotPresent( + ABSL_CHECK(InsertIfNotPresent( &stream_index_to_stream_id_, options.audio_stream(*options_index_ptr).stream_index(), stream_id)); - CHECK(InsertIfNotPresent(&stream_id_to_audio_options_index_, + ABSL_CHECK(InsertIfNotPresent(&stream_id_to_audio_options_index_, stream_id, *options_index_ptr)); - CHECK(InsertIfNotPresent(&audio_options_index_to_stream_id, + ABSL_CHECK(InsertIfNotPresent(&audio_options_index_to_stream_id, *options_index_ptr, stream_id)); } ++current_audio_index; @@ -772,7 +773,7 @@ av_packet->data = nullptr; int ret = av_read_frame(avformat_ctx_, av_packet.get()); if (ret >= 0) { - CHECK(av_packet->data) << "AVPacket does not include any data but " + ABSL_CHECK(av_packet->data) << "AVPacket does not include any data but " "av_read_frame was successful."; const int stream_id = av_packet->stream_index; auto audio_iterator = audio_processor_.find(stream_id);
diff --git a/third_party/mediapipe/src/mediapipe/util/filtering/relative_velocity_filter.cc b/third_party/mediapipe/src/mediapipe/util/filtering/relative_velocity_filter.cc index ab88ad5..9fb0e2d 100644 --- a/third_party/mediapipe/src/mediapipe/util/filtering/relative_velocity_filter.cc +++ b/third_party/mediapipe/src/mediapipe/util/filtering/relative_velocity_filter.cc
@@ -19,6 +19,7 @@ #include "absl/memory/memory.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -36,7 +37,7 @@ if (last_timestamp_ == -1) { alpha = 1.0; } else { - DCHECK(distance_mode_ == DistanceEstimationMode::kLegacyTransition || + ABSL_DCHECK(distance_mode_ == DistanceEstimationMode::kLegacyTransition || distance_mode_ == DistanceEstimationMode::kForceCurrentScale); const float distance = distance_mode_ == DistanceEstimationMode::kLegacyTransition
diff --git a/third_party/mediapipe/src/mediapipe/util/image_frame_util.cc b/third_party/mediapipe/src/mediapipe/util/image_frame_util.cc index d276942..3ab28df5 100644 --- a/third_party/mediapipe/src/mediapipe/util/image_frame_util.cc +++ b/third_party/mediapipe/src/mediapipe/util/image_frame_util.cc
@@ -20,7 +20,6 @@ #include <string> #include <vector> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/strings/string_view.h" @@ -38,6 +37,7 @@ #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/port.h" #include "mediapipe/framework/port/status_macros.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -47,7 +47,7 @@ const int height, const int alignment_boundary, const int open_cv_interpolation_algorithm, ImageFrame* destination_frame) { - CHECK(destination_frame); + ABSL_CHECK(destination_frame); ABSL_CHECK_EQ(ImageFormat::SRGB, source_frame.Format()); cv::Mat source_mat = ::mediapipe::formats::MatView(&source_frame); @@ -62,7 +62,7 @@ void RescaleSrgbImage(const cv::Mat& source, const int width, const int height, const int open_cv_interpolation_algorithm, cv::Mat* destination) { - CHECK(destination); + ABSL_CHECK(destination); // Convert input_mat into 16 bit per channel linear RGB space. cv::Mat input_mat16; @@ -142,7 +142,7 @@ void YUVImageToImageFrame(const YUVImage& yuv_image, ImageFrame* image_frame, bool use_bt709) { - CHECK(image_frame); + ABSL_CHECK(image_frame); int width = yuv_image.width(); int height = yuv_image.height(); image_frame->Reset(ImageFormat::SRGB, width, height, 16); @@ -167,7 +167,7 @@ void YUVImageToImageFrameFromFormat(const YUVImage& yuv_image, ImageFrame* image_frame) { - CHECK(image_frame); + ABSL_CHECK(image_frame); int width = yuv_image.width(); int height = yuv_image.height(); image_frame->Reset(ImageFormat::SRGB, width, height, 16);
diff --git a/third_party/mediapipe/src/mediapipe/util/pose_util.cc b/third_party/mediapipe/src/mediapipe/util/pose_util.cc index 4a6bb6c..c68256c 100644 --- a/third_party/mediapipe/src/mediapipe/util/pose_util.cc +++ b/third_party/mediapipe/src/mediapipe/util/pose_util.cc
@@ -192,15 +192,20 @@ } } -void DrawFace(const mediapipe::NormalizedLandmarkList& face, bool flip_y, - bool draw_nose, int color_style, bool reverse_color, +void DrawFace(const mediapipe::NormalizedLandmarkList& face, + const std::pair<int, int>& image_size, const cv::Mat& affine, + bool flip_y, bool draw_nose, int color_style, bool reverse_color, int draw_line_width, cv::Mat* image) { - const int target_width = image->cols; - const int target_height = image->rows; std::vector<cv::Point> landmarks; for (const auto& lm : face.landmark()) { - landmarks.emplace_back(lm.x() * target_width, - (flip_y ? 1.0f - lm.y() : lm.y()) * target_height); + float ori_x = lm.x() * image_size.first; + float ori_y = (flip_y ? 1.0f - lm.y() : lm.y()) * image_size.second; + + landmarks.emplace_back( + affine.at<float>(0, 0) * ori_x + affine.at<float>(0, 1) * ori_y + + affine.at<float>(0, 2), + affine.at<float>(1, 0) * ori_x + affine.at<float>(1, 1) * ori_y + + affine.at<float>(1, 2)); } cv::Scalar kFaceOvalColor;
diff --git a/third_party/mediapipe/src/mediapipe/util/pose_util.h b/third_party/mediapipe/src/mediapipe/util/pose_util.h index da952422..aeb2b92 100644 --- a/third_party/mediapipe/src/mediapipe/util/pose_util.h +++ b/third_party/mediapipe/src/mediapipe/util/pose_util.h
@@ -23,8 +23,9 @@ void DrawPose(const mediapipe::NormalizedLandmarkList& pose, bool flip_y, cv::Mat* image); -void DrawFace(const mediapipe::NormalizedLandmarkList& face, bool flip_y, - bool draw_nose, int color_style, bool reverse_color, +void DrawFace(const mediapipe::NormalizedLandmarkList& face, + const std::pair<int, int>& image_size, const cv::Mat& affine, + bool flip_y, bool draw_nose, int color_style, bool reverse_color, int draw_line_width, cv::Mat* image); } // namespace mediapipe
diff --git a/third_party/mediapipe/src/mediapipe/util/resource_cache.h b/third_party/mediapipe/src/mediapipe/util/resource_cache.h index da0e4adf..5e3ecb1 100644 --- a/third_party/mediapipe/src/mediapipe/util/resource_cache.h +++ b/third_party/mediapipe/src/mediapipe/util/resource_cache.h
@@ -19,8 +19,8 @@ #include "absl/container/flat_hash_map.h" #include "absl/functional/function_ref.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -44,9 +44,7 @@ ABSL_CHECK_EQ(entry->request_count, 0); entry->request_count = 1; entry_list_.Append(entry); - if (entry->prev != nullptr) { - ABSL_CHECK_GE(entry->prev->request_count, 1); - } + if (entry->prev != nullptr) ABSL_CHECK_GE(entry->prev->request_count, 1); } else { entry = map_it->second.get(); ++entry->request_count;
diff --git a/third_party/mediapipe/src/mediapipe/util/sequence/README.md b/third_party/mediapipe/src/mediapipe/util/sequence/README.md index 9facf876..960a0d9b 100644 --- a/third_party/mediapipe/src/mediapipe/util/sequence/README.md +++ b/third_party/mediapipe/src/mediapipe/util/sequence/README.md
@@ -555,9 +555,9 @@ |`PREFIX/feature/dimensions`|context int list|`set_feature_dimensions` / `SetFeatureDimensions`|A list of integer dimensions for each feature.| |`PREFIX/feature/rate`|context float|`set_feature_rate` / `SetFeatureRate`|The rate that features are calculated as features per second.| |`PREFIX/feature/bytes/format`|context bytes|`set_feature_bytes_format` / `SetFeatureBytesFormat`|The encoding format if any for features stored as bytes.| -|`PREFIX/context_feature/floats`|context float list|`add_context_feature_floats` / `AddContextFeatureFloats`|A list of floats for the entire example.| -|`PREFIX/context_feature/bytes`|context bytes list|`add_context_feature_bytes` / `AddContextFeatureBytes`|A list of bytes for the entire example. Maybe be encoded.| -|`PREFIX/context_feature/ints`|context int list|`add_context_feature_ints` / `AddContextFeatureInts`|A list of ints for the entire example.| +|`PREFIX/context_feature/floats`|context float list|`set_context_feature_floats` / `AddContextFeatureFloats`|A list of floats for the entire example.| +|`PREFIX/context_feature/bytes`|context bytes list|`set_context_feature_bytes` / `AddContextFeatureBytes`|A list of bytes for the entire example. Maybe be encoded.| +|`PREFIX/context_feature/ints`|context int list|`set_context_feature_ints` / `AddContextFeatureInts`|A list of ints for the entire example.| ### Keys related to audio Audio is a special subtype of generic features with additional data about the
diff --git a/third_party/mediapipe/src/mediapipe/util/sequence/media_sequence.cc b/third_party/mediapipe/src/mediapipe/util/sequence/media_sequence.cc index 372a2c61..95487c6 100644 --- a/third_party/mediapipe/src/mediapipe/util/sequence/media_sequence.cc +++ b/third_party/mediapipe/src/mediapipe/util/sequence/media_sequence.cc
@@ -17,11 +17,11 @@ #include <cmath> #include <limits> -#include "absl/log/absl_check.h" #include "absl/strings/str_split.h" #include "mediapipe/framework/port/opencv_imgcodecs_inc.h" #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/util/sequence/media_sequence_util.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace mediasequence { @@ -531,7 +531,7 @@ const std::string& prefix, const tensorflow::SequenceExample& sequence, int index) { const auto& flat_data = GetFeatureFloatsAt(prefix, sequence, index); - CHECK(HasFeatureNumChannels(prefix, sequence)) + ABSL_CHECK(HasFeatureNumChannels(prefix, sequence)) << "GetAudioAt requires num_channels context to be specified as key: " << merge_prefix(prefix, kFeatureNumChannelsKey); int num_channels = GetFeatureNumChannels(prefix, sequence);
diff --git a/third_party/mediapipe/src/mediapipe/util/sequence/media_sequence_util.h b/third_party/mediapipe/src/mediapipe/util/sequence/media_sequence_util.h index 2e73828..867b116 100644 --- a/third_party/mediapipe/src/mediapipe/util/sequence/media_sequence_util.h +++ b/third_party/mediapipe/src/mediapipe/util/sequence/media_sequence_util.h
@@ -92,12 +92,12 @@ #include <string> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/proto_ns.h" #include "tensorflow/core/example/example.pb.h" #include "tensorflow/core/example/feature.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace mediasequence { @@ -125,7 +125,7 @@ // proto map's at function also checks whether key is present, but it doesn't // print the missing key when it check-fails. const auto it = sequence.context().feature().find(key); - CHECK(it != sequence.context().feature().end()) + ABSL_CHECK(it != sequence.context().feature().end()) << "Could not find context key " << key << ". Sequence: \n" << sequence.DebugString(); return it->second;
diff --git a/third_party/mediapipe/src/mediapipe/util/tflite/cpu_op_resolver.cc b/third_party/mediapipe/src/mediapipe/util/tflite/cpu_op_resolver.cc index 588a237b..1b4547a9 100644 --- a/third_party/mediapipe/src/mediapipe/util/tflite/cpu_op_resolver.cc +++ b/third_party/mediapipe/src/mediapipe/util/tflite/cpu_op_resolver.cc
@@ -23,11 +23,12 @@ #include "mediapipe/util/tflite/operations/transpose_conv_bias.h" #include "tensorflow/lite/builtin_op_data.h" #include "tensorflow/lite/mutable_op_resolver.h" +#include "absl/log/absl_check.h" namespace mediapipe { void MediaPipe_RegisterTfLiteOpResolver(tflite::MutableOpResolver *resolver) { - CHECK(resolver != nullptr); + ABSL_CHECK(resolver != nullptr); resolver->AddCustom("MaxPoolingWithArgmax2D", tflite_operations::RegisterMaxPoolingWithArgmax2D()); resolver->AddCustom("MaxUnpooling2D",
diff --git a/third_party/mediapipe/src/mediapipe/util/time_series_util.cc b/third_party/mediapipe/src/mediapipe/util/time_series_util.cc index 128cc117..dbb5490 100644 --- a/third_party/mediapipe/src/mediapipe/util/time_series_util.cc +++ b/third_party/mediapipe/src/mediapipe/util/time_series_util.cc
@@ -19,11 +19,11 @@ #include <iostream> #include <string> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/formats/time_series_header.pb.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" namespace mediapipe { namespace time_series_util { @@ -80,7 +80,7 @@ absl::Status FillTimeSeriesHeaderIfValid(const Packet& header_packet, TimeSeriesHeader* header) { - CHECK(header); + ABSL_CHECK(header); if (header_packet.IsEmpty()) { return tool::StatusFail("No header found."); } @@ -93,7 +93,7 @@ absl::Status FillMultiStreamTimeSeriesHeaderIfValid( const Packet& header_packet, MultiStreamTimeSeriesHeader* header) { - CHECK(header); + ABSL_CHECK(header); if (header_packet.IsEmpty()) { return tool::StatusFail("No header found."); }
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/box_detector.cc b/third_party/mediapipe/src/mediapipe/util/tracking/box_detector.cc index 44567d7..37e13e4 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/box_detector.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/box_detector.cc
@@ -16,7 +16,6 @@ #include <vector> -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "mediapipe/framework/port/opencv_calib3d_inc.h" #include "mediapipe/framework/port/opencv_imgproc_inc.h" @@ -24,6 +23,7 @@ #include "mediapipe/util/tracking/box_detector.pb.h" #include "mediapipe/util/tracking/box_tracker.h" #include "mediapipe/util/tracking/measure_time.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -44,7 +44,7 @@ } cv::Mat ConvertDescriptorsToMat(const std::vector<std::string> &descriptors) { - CHECK(!descriptors.empty()) << "empty descriptors."; + ABSL_CHECK(!descriptors.empty()) << "empty descriptors."; const int descriptors_dims = descriptors[0].size(); ABSL_CHECK_GT(descriptors_dims, 0); @@ -683,7 +683,7 @@ } ABSL_CHECK_EQ(frame_entry.keypoints_size(), - frame_entry.descriptors_size() * 2); + frame_entry.descriptors_size() * 2); const int num_features = frame_entry.descriptors_size(); ABSL_CHECK_GT(num_features, 0);
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/box_tracker.cc b/third_party/mediapipe/src/mediapipe/util/tracking/box_tracker.cc index 61a9437b..f2205f72 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/box_tracker.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/box_tracker.cc
@@ -19,7 +19,6 @@ #include <fstream> #include <limits> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "absl/synchronization/mutex.h" #include "absl/time/clock.h" @@ -28,6 +27,7 @@ #include "mediapipe/framework/port/logging.h" #include "mediapipe/util/tracking/measure_time.h" #include "mediapipe/util/tracking/tracking.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -38,7 +38,7 @@ void MotionBoxStateQuadToVertices(const MotionBoxState::Quad& quad, std::vector<Vector2_f>* vertices) { ABSL_CHECK_EQ(TimedBox::kNumQuadVertices * 2, quad.vertices_size()); - CHECK(vertices != nullptr); + ABSL_CHECK(vertices != nullptr); vertices->clear(); for (int i = 0; i < TimedBox::kNumQuadVertices; ++i) { vertices->push_back( @@ -49,7 +49,7 @@ void VerticesToMotionBoxStateQuad(const std::vector<Vector2_f>& vertices, MotionBoxState::Quad* quad) { ABSL_CHECK_EQ(TimedBox::kNumQuadVertices, vertices.size()); - CHECK(quad != nullptr); + ABSL_CHECK(quad != nullptr); for (const Vector2_f& vertex : vertices) { quad->add_vertices(vertex.x()); quad->add_vertices(vertex.y()); @@ -57,7 +57,7 @@ } void MotionBoxStateFromTimedBox(const TimedBox& box, MotionBoxState* state) { - CHECK(state); + ABSL_CHECK(state); state->set_pos_x(box.left); state->set_pos_y(box.top); state->set_width(box.right - box.left); @@ -91,7 +91,7 @@ } void TimedBoxFromMotionBoxState(const MotionBoxState& state, TimedBox* box) { - CHECK(box); + ABSL_CHECK(box); const float scale_dx = state.width() * (state.scale() - 1.0f) * 0.5f; const float scale_dy = state.height() * (state.scale() - 1.0f) * 0.5f; box->left = state.pos_x() - scale_dx; @@ -486,12 +486,12 @@ bool BoxTracker::GetTimedPosition(int id, int64_t time_msec, TimedBox* result, std::vector<MotionBoxState>* states) { - CHECK(result); + ABSL_CHECK(result); MotionBoxState* lhs_box_state = nullptr; MotionBoxState* rhs_box_state = nullptr; if (states) { - CHECK(options_.record_path_states()) + ABSL_CHECK(options_.record_path_states()) << "Requesting corresponding tracking states requires option " << "record_path_states to be set"; states->resize(1); @@ -908,7 +908,7 @@ bool TimedBoxAtTime(const PathSegment& segment, int64_t time_msec, TimedBox* box, MotionBoxState* state) { - CHECK(box); + ABSL_CHECK(box); if (segment.empty()) { return false; @@ -1032,7 +1032,7 @@ bool BoxTracker::GetTrackingData(int id, int64_t request_time_msec, TrackingData* tracking_data, int* tracking_data_msec) { - CHECK(tracking_data); + ABSL_CHECK(tracking_data); int chunk_idx = ChunkIdxFromTime(request_time_msec);
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/camera_motion.cc b/third_party/mediapipe/src/mediapipe/util/tracking/camera_motion.cc index b2c137c8..3497cbe 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/camera_motion.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/camera_motion.cc
@@ -16,9 +16,9 @@ #include <numeric> -#include "absl/log/absl_check.h" #include "absl/strings/str_format.h" #include "mediapipe/util/tracking/region_flow.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -185,7 +185,7 @@ void SubtractCameraMotionFromFeatures( const std::vector<CameraMotion>& camera_motions, std::vector<RegionFlowFeatureList*>* feature_lists) { - CHECK(feature_lists != nullptr); + ABSL_CHECK(feature_lists != nullptr); ABSL_CHECK_GE(camera_motions.size(), feature_lists->size()); if (feature_lists->empty()) { return;
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/camera_motion.h b/third_party/mediapipe/src/mediapipe/util/tracking/camera_motion.h index 379920b1..12477d858 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/camera_motion.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/camera_motion.h
@@ -17,10 +17,10 @@ #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/util/tracking/camera_motion.pb.h" #include "mediapipe/util/tracking/motion_models.h" #include "mediapipe/util/tracking/region_flow.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -238,10 +238,10 @@ std::vector<CameraMotion::Type>* downsampled_types) { if (model_type) { ABSL_CHECK_EQ(models.size(), model_type->size()); - CHECK(downsampled_models) << "Expecting output models."; + ABSL_CHECK(downsampled_models) << "Expecting output models."; } - CHECK(downsampled_models); + ABSL_CHECK(downsampled_models); downsampled_models->clear(); if (downsampled_types) { downsampled_types->clear(); @@ -277,7 +277,7 @@ template <class Container> void SubsampleEntities(const Container& input, int downsample_factor, Container* output) { - CHECK(output); + ABSL_CHECK(output); output->clear(); if (input.empty()) {
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/flow_packager.cc b/third_party/mediapipe/src/mediapipe/util/tracking/flow_packager.cc index 01ee6f6..2ec81db 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/flow_packager.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/flow_packager.cc
@@ -20,7 +20,6 @@ #include <cmath> #include <memory> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "mediapipe/framework/port/logging.h" @@ -31,6 +30,7 @@ #include "mediapipe/util/tracking/motion_models.h" #include "mediapipe/util/tracking/motion_models.pb.h" #include "mediapipe/util/tracking/region_flow.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -106,7 +106,7 @@ template <typename T> inline bool DecodeFromStringView(absl::string_view str, T* result) { - CHECK(result != nullptr); + ABSL_CHECK(result != nullptr); if (sizeof(*result) != str.size()) { return false; } @@ -117,7 +117,7 @@ template <typename T> inline bool DecodeVectorFromStringView(absl::string_view str, std::vector<T>* result) { - CHECK(result != nullptr); + ABSL_CHECK(result != nullptr); if (str.size() % sizeof(T) != 0) return false; result->clear(); result->reserve(str.size() / sizeof(T)); @@ -135,7 +135,7 @@ void FlowPackager::PackFlow(const RegionFlowFeatureList& feature_list, const CameraMotion* camera_motion, TrackingData* tracking_data) const { - CHECK(tracking_data); + ABSL_CHECK(tracking_data); ABSL_CHECK_GT(feature_list.frame_width(), 0); ABSL_CHECK_GT(feature_list.frame_height(), 0); @@ -274,8 +274,8 @@ void FlowPackager::EncodeTrackingData(const TrackingData& tracking_data, BinaryTrackingData* binary_data) const { - CHECK(options_.binary_tracking_data_support()); - CHECK(binary_data != nullptr); + ABSL_CHECK(options_.binary_tracking_data_support()); + ABSL_CHECK(binary_data != nullptr); int32_t frame_flags = 0; const bool high_profile = options_.use_high_profile(); @@ -603,7 +603,7 @@ void FlowPackager::DecodeTrackingData(const BinaryTrackingData& container_data, TrackingData* tracking_data) const { - CHECK(tracking_data != nullptr); + ABSL_CHECK(tracking_data != nullptr); absl::string_view data(container_data.data()); int32_t frame_flags = 0; @@ -790,7 +790,7 @@ void FlowPackager::BinaryTrackingDataToContainer( const BinaryTrackingData& binary_data, TrackingContainer* container) const { - CHECK(container != nullptr); + ABSL_CHECK(container != nullptr); container->Clear(); container->set_header("TRAK"); container->set_version(1); @@ -807,7 +807,7 @@ void FlowPackager::DecodeMetaData(const TrackingContainer& container_data, MetaData* meta_data) const { - CHECK(meta_data != nullptr); + ABSL_CHECK(meta_data != nullptr); ABSL_CHECK_EQ("META", container_data.header()); ABSL_CHECK_EQ(1, container_data.version()) << "Unsupported version."; @@ -834,7 +834,7 @@ void FlowPackager::FinalizeTrackingContainerFormat( std::vector<uint32_t>* timestamps, TrackingContainerFormat* container_format) { - CHECK(container_format != nullptr); + ABSL_CHECK(container_format != nullptr); // Compute binary sizes of track_data. const int num_frames = container_format->track_data_size(); @@ -878,7 +878,7 @@ void FlowPackager::FinalizeTrackingContainerProto( std::vector<uint32_t>* timestamps, TrackingContainerProto* proto) { - CHECK(proto != nullptr); + ABSL_CHECK(proto != nullptr); // Compute binary sizes of track_data. const int num_frames = proto->track_data_size(); @@ -924,7 +924,7 @@ void FlowPackager::AddContainerToString(const TrackingContainer& container, std::string* binary_data) { - CHECK(binary_data != nullptr); + ABSL_CHECK(binary_data != nullptr); std::string header_string(container.header()); ABSL_CHECK_EQ(4, header_string.size()); @@ -937,10 +937,10 @@ std::string FlowPackager::SplitContainerFromString( absl::string_view* binary_data, TrackingContainer* container) { - CHECK(binary_data != nullptr); - CHECK(container != nullptr); + ABSL_CHECK(binary_data != nullptr); + ABSL_CHECK(container != nullptr); ABSL_CHECK_GE(binary_data->size(), 12) << "Data does not contain " - << "valid container"; + << "valid container"; container->set_header(PopSubstring(4, binary_data)); @@ -962,7 +962,7 @@ void FlowPackager::TrackingContainerFormatToBinary( const TrackingContainerFormat& container_format, std::string* binary) { - CHECK(binary != nullptr); + ABSL_CHECK(binary != nullptr); binary->clear(); AddContainerToString(container_format.meta_data(), binary); @@ -975,13 +975,13 @@ void FlowPackager::TrackingContainerFormatFromBinary( const std::string& binary, TrackingContainerFormat* container_format) { - CHECK(container_format != nullptr); + ABSL_CHECK(container_format != nullptr); container_format->Clear(); absl::string_view data(binary); ABSL_CHECK_EQ("META", SplitContainerFromString( - &data, container_format->mutable_meta_data())); + &data, container_format->mutable_meta_data())); MetaData meta_data; DecodeMetaData(container_format->meta_data(), &meta_data); @@ -991,12 +991,12 @@ } ABSL_CHECK_EQ("TERM", SplitContainerFromString( - &data, container_format->mutable_term_data())); + &data, container_format->mutable_term_data())); } void FlowPackager::SortRegionFlowFeatureList( float scale_x, float scale_y, RegionFlowFeatureList* feature_list) const { - CHECK(feature_list != nullptr); + ABSL_CHECK(feature_list != nullptr); // Sort features lexicographically. std::sort(feature_list->mutable_feature()->begin(), feature_list->mutable_feature()->end(),
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/image_util.cc b/third_party/mediapipe/src/mediapipe/util/tracking/image_util.cc index a7e4a2f4..f234b61 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/image_util.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/image_util.cc
@@ -17,16 +17,16 @@ #include <algorithm> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/util/tracking/motion_models.h" #include "mediapipe/util/tracking/region_flow.h" +#include "absl/log/absl_check.h" namespace mediapipe { // Returns median of the L1 color distance between img_1 and img_2 float FrameDifferenceMedian(const cv::Mat& img_1, const cv::Mat& img_2) { - CHECK(img_1.size() == img_2.size()); + ABSL_CHECK(img_1.size() == img_2.size()); ABSL_CHECK_EQ(img_1.channels(), img_2.channels()); std::vector<float> color_diffs; @@ -53,7 +53,7 @@ } void JetColoring(int steps, std::vector<Vector3_f>* color_map) { - CHECK(color_map != nullptr); + ABSL_CHECK(color_map != nullptr); color_map->resize(steps); for (int i = 0; i < steps; ++i) { const float frac = 2.0f * (i * (1.0f / steps) - 0.5f);
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/image_util.h b/third_party/mediapipe/src/mediapipe/util/tracking/image_util.h index f1e7eda..3a77617 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/image_util.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/image_util.h
@@ -17,12 +17,12 @@ #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/opencv_core_inc.h" #include "mediapipe/framework/port/opencv_imgproc_inc.h" #include "mediapipe/framework/port/vector.h" #include "mediapipe/util/tracking/motion_models.pb.h" #include "mediapipe/util/tracking/region_flow.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe {
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/measure_time.h b/third_party/mediapipe/src/mediapipe/util/tracking/measure_time.h index 0351f46..622f808 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/measure_time.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/measure_time.h
@@ -37,6 +37,7 @@ #include "absl/time/clock.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/logging.h" +#include "absl/log/absl_check.h" extern bool flags_measure_time; @@ -101,7 +102,7 @@ show_output_(show_output), accumulator_(accumulator) { if (show_output_) { - CHECK(accumulator_); + ABSL_CHECK(accumulator_); start_time_ = GetWallTime(); } }
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/motion_analysis.cc b/third_party/mediapipe/src/mediapipe/util/tracking/motion_analysis.cc index d7509774..e968392a 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/motion_analysis.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/motion_analysis.cc
@@ -20,7 +20,6 @@ #include <deque> #include <memory> -#include "absl/log/absl_check.h" #include "absl/strings/str_format.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/logging.h" @@ -35,6 +34,7 @@ #include "mediapipe/util/tracking/region_flow_computation.h" #include "mediapipe/util/tracking/region_flow_computation.pb.h" #include "mediapipe/util/tracking/region_flow_visualization.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -94,7 +94,7 @@ if (compute_feature_descriptors_) { ABSL_CHECK_EQ(RegionFlowComputationOptions::FORMAT_RGB, - options_.flow_options().image_format()) + options_.flow_options().image_format()) << "Feature descriptors only support RGB currently."; prev_frame_.reset(new cv::Mat(frame_height_, frame_width_, CV_8UC3)); } @@ -363,7 +363,7 @@ RegionFlowFeatureList* output_feature_list) { // Don't check input sizes here, RegionFlowComputation does that based // on its internal options. - CHECK(feature_computation_) << "Calls to AddFrame* can NOT be mixed " + ABSL_CHECK(feature_computation_) << "Calls to AddFrame* can NOT be mixed " << "with AddFeatures"; // Compute RegionFlow. @@ -462,7 +462,7 @@ void MotionAnalysis::EnqueueFeaturesAndMotions( const RegionFlowFeatureList& features, const CameraMotion& motion) { feature_computation_ = false; - CHECK(buffer_->HaveEqualSize({"motion", "features"})) + ABSL_CHECK(buffer_->HaveEqualSize({"motion", "features"})) << "Can not be mixed with other Add* calls"; buffer_->EmplaceDatum("features", new RegionFlowFeatureList(features)); buffer_->EmplaceDatum("motion", new CameraMotion(motion)); @@ -515,7 +515,7 @@ } } - CHECK(buffer_->HaveEqualSize({"features", "motion"})); + ABSL_CHECK(buffer_->HaveEqualSize({"features", "motion"})); if (compute_saliency) { ComputeSaliency(); @@ -531,7 +531,7 @@ const bool compute_saliency = options_.compute_motion_saliency(); ABSL_CHECK_EQ(compute_saliency, saliency != nullptr) << "Computing saliency requires saliency output and vice versa"; - CHECK(buffer_->HaveEqualSize({"features", "motion"})); + ABSL_CHECK(buffer_->HaveEqualSize({"features", "motion"})); // Discard prev. overlap (already output, just used for filtering here). buffer_->DiscardData(buffer_->AllTags(), prev_overlap_start_); @@ -601,7 +601,7 @@ prev_overlap_start_ = num_output_frames - new_overlap_start; ABSL_CHECK_GE(prev_overlap_start_, 0); - CHECK(buffer_->TruncateBuffer(flush)); + ABSL_CHECK(buffer_->TruncateBuffer(flush)); overlap_start_ = buffer_->MaxBufferSize(); return num_output_frames; @@ -612,7 +612,7 @@ const SalientPointFrame* saliency, cv::Mat* rendered_results) { #ifndef NO_RENDERING - CHECK(rendered_results != nullptr); + ABSL_CHECK(rendered_results != nullptr); ABSL_CHECK_EQ(frame_width_, rendered_results->cols); ABSL_CHECK_EQ(frame_height_, rendered_results->rows); @@ -699,7 +699,7 @@ &foreground_weights); // Setup push pull map (with border). Ensure constructor used the right type. - CHECK(foreground_push_pull_->filter_type() == + ABSL_CHECK(foreground_push_pull_->filter_type() == PushPullFilteringC1::BINOMIAL_5X5 || foreground_push_pull_->filter_type() == PushPullFilteringC1::GAUSSIAN_5X5); @@ -742,8 +742,8 @@ void MotionAnalysis::VisualizeDenseForeground(const cv::Mat& foreground_mask, cv::Mat* output) { - CHECK(output != nullptr); - CHECK(foreground_mask.size() == output->size()); + ABSL_CHECK(output != nullptr); + ABSL_CHECK(foreground_mask.size() == output->size()); // Map foreground measure to color (green by default). std::vector<Vector3_f> color_map; if (options_.visualization_options().foreground_jet_coloring()) { @@ -781,7 +781,7 @@ } void MotionAnalysis::VisualizeBlurAnalysisRegions(cv::Mat* input_view) { - CHECK(input_view != nullptr); + ABSL_CHECK(input_view != nullptr); cv::Mat intensity; cv::cvtColor(*input_view, intensity, cv::COLOR_RGB2GRAY); @@ -822,7 +822,7 @@ buffer_->AddDatum("saliency", std::move(saliency)); } - CHECK(buffer_->HaveEqualSize({"features", "motion", "saliency"})); + ABSL_CHECK(buffer_->HaveEqualSize({"features", "motion", "saliency"})); // Clear output saliency and copy from saliency. buffer_->DiscardDatum("output_saliency",
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/motion_estimation.cc b/third_party/mediapipe/src/mediapipe/util/tracking/motion_estimation.cc index 1da6496..048aeec 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/motion_estimation.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/motion_estimation.cc
@@ -31,7 +31,6 @@ #include "Eigen/SVD" #include "absl/container/node_hash_map.h" #include "absl/container/node_hash_set.h" -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/util/tracking/camera_motion.h" @@ -41,6 +40,7 @@ #include "mediapipe/util/tracking/parallel_invoker.h" #include "mediapipe/util/tracking/region_flow.h" #include "mediapipe/util/tracking/region_flow.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -174,7 +174,7 @@ // estimated translation. void MotionPrior(const RegionFlowFeatureList& feature_list, std::vector<float>* motion_prior) { - CHECK(motion_prior != nullptr); + ABSL_CHECK(motion_prior != nullptr); const int num_features = feature_list.feature_size(); ABSL_CHECK_EQ(num_features, motion_prior->size()); @@ -351,7 +351,7 @@ // feature's irls weight. If weight_backup is set, allocates storage // to backup and reset irls weights. void AllocateIRLSWeightStorage(bool weight_backup) { - CHECK(feature_lists != nullptr); + ABSL_CHECK(feature_lists != nullptr); const int num_frames = feature_lists->size(); if (weight_backup) { irls_weight_backup = &irls_backup_storage; @@ -381,7 +381,7 @@ // Returns number of frames in this clip. int num_frames() const { - DCHECK(feature_lists); + ABSL_DCHECK(feature_lists); return feature_lists->size(); } @@ -397,8 +397,8 @@ // Checks that SingleTrackClipData is properly initialized. void CheckInitialization() const { - CHECK(feature_lists != nullptr); - CHECK(camera_motions != nullptr); + ABSL_CHECK(feature_lists != nullptr); + ABSL_CHECK(camera_motions != nullptr); ABSL_CHECK_EQ(feature_lists->size(), camera_motions->size()); if (feature_lists->empty()) { return; @@ -505,11 +505,11 @@ << "Option estimate_similarity is deprecated, use static function " << "EstimateSimilarityModelL2 instead."; ABSL_CHECK_NE(options.linear_similarity_estimation(), - MotionEstimationOptions::ESTIMATION_LS_L2_RANSAC) + MotionEstimationOptions::ESTIMATION_LS_L2_RANSAC) << "Option ESTIMATION_LS_L2_RANSAC is deprecated, use " << "ESTIMATION_LS_IRLS instead."; ABSL_CHECK_NE(options.linear_similarity_estimation(), - MotionEstimationOptions::ESTIMATION_LS_L1) + MotionEstimationOptions::ESTIMATION_LS_L1) << "Option ESTIMATION_LS_L1 is deprecated, use static function " << "EstimateLinearSimilarityL1 instead."; @@ -564,7 +564,7 @@ } case MotionEstimationOptions::TEMPORAL_IRLS_MASK: - CHECK(options.irls_initialization().activated()) + ABSL_CHECK(options.irls_initialization().activated()) << "To use dependent_initialization, irls_initialization has to " << "be activated. "; inlier_mask_.reset(new InlierMask(options.irls_mask_options(), @@ -579,11 +579,11 @@ const int* intensity_frame, // null const int* prev_intensity_frame, // null CameraMotion* camera_motion) const { - CHECK(camera_motion); + ABSL_CHECK(camera_motion); - CHECK(intensity_frame == NULL) + ABSL_CHECK(intensity_frame == NULL) << "Parameter intensity_frame is deprecated, must be NULL."; - CHECK(prev_intensity_frame == NULL) + ABSL_CHECK(prev_intensity_frame == NULL) << "Parameter prev_intensity_frame is deprecated, must be NULL."; RegionFlowFeatureList feature_list; @@ -822,8 +822,8 @@ std::vector<CameraMotion>* camera_motions) const { MEASURE_TIME << "Estimate motions: " << feature_lists->size(); - CHECK(feature_lists != nullptr); - CHECK(camera_motions != nullptr); + ABSL_CHECK(feature_lists != nullptr); + ABSL_CHECK(camera_motions != nullptr); const int num_frames = feature_lists->size(); ABSL_CHECK_EQ(num_frames, camera_motions->size()); @@ -1081,8 +1081,7 @@ // Estimate mixtures across a spectrum a different regularizers, from the // weakest to the most regularized one. const int num_mixture_levels = options_.mixture_regularizer_levels(); - ABSL_CHECK_LE(num_mixture_levels, 10) - << "Only up to 10 mixtures are supported."; + ABSL_CHECK_LE(num_mixture_levels, 10) << "Only up to 10 mixtures are supported."; // Initialize to weakest regularizer. float regularizer = options_.mixture_regularizer(); @@ -1127,7 +1126,7 @@ for (const CameraMotion& motion : *camera_motions) { if (motion.mixture_homography_spectrum_size() > 0) { ABSL_CHECK_EQ(motion.mixture_homography_spectrum_size(), - options_.mixture_regularizer_levels()); + options_.mixture_regularizer_levels()); } } @@ -1166,7 +1165,7 @@ const EstimateModelOptions& model_options, const MotionEstimationThreadStorage* thread_storage, std::vector<SingleTrackClipData>* clip_datas) const { - CHECK(clip_datas != nullptr); + ABSL_CHECK(clip_datas != nullptr); const int num_datas = clip_datas->size(); if (num_datas == 0) { @@ -1268,7 +1267,7 @@ // Traverse frames in order. for (int k = 0; k < clip_data.num_frames(); ++k) { if (clip_data.feature_lists->at(k)->feature_size() > 0) { - CHECK(clip_data.feature_lists->at(k)->long_tracks()) + ABSL_CHECK(clip_data.feature_lists->at(k)->long_tracks()) << "Estimation policy TEMPORAL_LONG_FEATURE_BIAS requires " << "tracking with long tracks."; } @@ -1283,7 +1282,7 @@ } if (clip_data.camera_motions->at(k).type() <= max_unstable_type) { - CHECK(clip_data.prior_weights[k].use_full_prior); + ABSL_CHECK(clip_data.prior_weights[k].use_full_prior); clip_data.prior_weights[k].alphas.assign(irls_per_round, 1.0f); clip_data.prior_weights[k].alphas.back() = 0.0; } @@ -1606,7 +1605,7 @@ const LongFeatureInfo& feature_info, const std::vector<float>& track_length_importance, std::vector<float>* irls_weights) const { - CHECK(irls_weights); + ABSL_CHECK(irls_weights); const int num_features = feature_list.feature_size(); if (num_features == 0) { return; @@ -1644,7 +1643,7 @@ void MotionEstimation::FeatureDensityNormalization( const RegionFlowFeatureList& feature_list, std::vector<float>* irls_weights) const { - CHECK(irls_weights); + ABSL_CHECK(irls_weights); const int num_features = feature_list.feature_size(); ABSL_CHECK_EQ(num_features, irls_weights->size()); @@ -1741,7 +1740,7 @@ if (options_.estimation_policy() == MotionEstimationOptions::TEMPORAL_LONG_FEATURE_BIAS) { ABSL_CHECK_NE(frame, -1) << "Only per frame processing for this policy " - << "supported."; + << "supported."; } IrlsInitializationInvoker invoker(type, max_unstable_type, model_options, @@ -1845,7 +1844,7 @@ void MotionEstimation::EnforceTrackConsistency( std::vector<SingleTrackClipData>* clip_datas) const { - CHECK(clip_datas != nullptr); + ABSL_CHECK(clip_datas != nullptr); if (clip_datas->empty()) { return; } @@ -1890,7 +1889,7 @@ void MotionEstimation::BiasFromFeatures( const RegionFlowFeatureList& feature_list, MotionType type, const EstimateModelOptions& model_options, std::vector<float>* bias) const { - CHECK(bias); + ABSL_CHECK(bias); const int num_features = feature_list.feature_size(); bias->resize(num_features); @@ -1930,8 +1929,8 @@ RegionFlowFeatureList* feature_list, MotionType type, const EstimateModelOptions& model_options, PriorFeatureWeights* prior_weights) const { - CHECK(prior_weights); - CHECK(feature_list); + ABSL_CHECK(prior_weights); + ABSL_CHECK(feature_list); // Don't bias duplicated frames -> should be identity transform. if (feature_list->is_duplicated()) { @@ -2061,7 +2060,7 @@ } } - DCHECK(spatial_bias->find(feature_ptr->track_id()) == + ABSL_DCHECK(spatial_bias->find(feature_ptr->track_id()) == spatial_bias->end()); // Threshold such that few similar tracks do not count. @@ -2233,7 +2232,7 @@ // Update feature's weight as well. feature.set_irls_weight(1.0f / (biased_weight + kIrlsEps)); } else { - CHECK(!update_irls_observation) << "Should never happen on >= 2nd round"; + ABSL_CHECK(!update_irls_observation) << "Should never happen on >= 2nd round"; // Not present, reset to spatial bias. const float biased_weight = spatial_bias[feature.track_id()].first; @@ -2258,7 +2257,7 @@ } void MotionEstimation::SmoothIRLSWeights(std::deque<float>* irls) const { - CHECK(irls != nullptr); + ABSL_CHECK(irls != nullptr); if (irls->empty()) { return; } @@ -2397,8 +2396,8 @@ void MotionEstimation::PolicyToIRLSRounds(int irls_rounds, int* total_rounds, int* irls_per_round) const { - CHECK(total_rounds != nullptr); - CHECK(irls_per_round != nullptr); + ABSL_CHECK(total_rounds != nullptr); + ABSL_CHECK(irls_per_round != nullptr); // Small optimization: irls_rounds == 0 -> total_rounds = 0 regardless of // settings. @@ -2432,8 +2431,8 @@ const std::vector<std::vector<float>>* reset_irls_weights, std::vector<RegionFlowFeatureList*>* feature_lists, std::vector<CameraMotion>* camera_motions) const { - CHECK(feature_lists != nullptr); - CHECK(camera_motions != nullptr); + ABSL_CHECK(feature_lists != nullptr); + ABSL_CHECK(camera_motions != nullptr); const int num_frames = feature_lists->size(); if (reset_irls_weights) { ABSL_DCHECK_EQ(num_frames, reset_irls_weights->size()); @@ -2584,7 +2583,7 @@ void MotionEstimation::ProjectMotionsDown( const MotionType& type, std::vector<CameraMotion>* camera_motions) const { - CHECK(camera_motions != nullptr); + ABSL_CHECK(camera_motions != nullptr); for (auto& camera_motion : *camera_motions) { switch (type) { case MODEL_AVERAGE_MAGNITUDE: @@ -2630,7 +2629,7 @@ void MotionEstimation::IRLSWeightFilter( std::vector<RegionFlowFeatureList*>* feature_lists) const { - CHECK(feature_lists != nullptr); + ABSL_CHECK(feature_lists != nullptr); for (auto feature_ptr : *feature_lists) { switch (options_.irls_weight_filter()) { case MotionEstimationOptions::IRLS_FILTER_TEXTURE: @@ -2657,7 +2656,7 @@ bool post_irls_weight_smoothing, std::vector<RegionFlowFeatureList*>* feature_lists, std::vector<CameraMotion>* camera_motions) const { - CHECK(camera_motions != nullptr); + ABSL_CHECK(camera_motions != nullptr); camera_motions->clear(); camera_motions->resize(feature_lists->size()); @@ -2698,7 +2697,7 @@ void MotionEstimation::DetermineShotBoundaries( const std::vector<RegionFlowFeatureList*>& feature_lists, std::vector<CameraMotion>* camera_motions) const { - CHECK(camera_motions != nullptr); + ABSL_CHECK(camera_motions != nullptr); ABSL_CHECK_EQ(feature_lists.size(), camera_motions->size()); const auto& shot_options = options_.shot_boundary_options(); @@ -2760,7 +2759,7 @@ void MotionEstimation::ResetMotionModels(const MotionEstimationOptions& options, CameraMotion* camera_motion) { - CHECK(camera_motion); + ABSL_CHECK(camera_motion); // Clear models. camera_motion->clear_translation(); @@ -3013,8 +3012,8 @@ void MotionEstimation::ComputeFeatureMask( const RegionFlowFeatureList& feature_list, std::vector<int>* mask_indices, std::vector<float>* bin_normalizer) const { - CHECK(mask_indices != nullptr); - CHECK(bin_normalizer != nullptr); + ABSL_CHECK(mask_indices != nullptr); + ABSL_CHECK(bin_normalizer != nullptr); const int num_features = feature_list.feature_size(); mask_indices->clear(); @@ -3049,7 +3048,7 @@ RegionFlowFeatureList* feature_list, const EstimateModelOptions& model_options, float avg_camera_motion, InlierMask* inlier_mask, TranslationModel* best_model) const { - CHECK(best_model != nullptr); + ABSL_CHECK(best_model != nullptr); const int num_features = feature_list->feature_size(); if (!num_features) { @@ -3271,9 +3270,9 @@ const RegionFlowFeatureList& feature_list, Eigen::Matrix<T, 4, 4>* matrix, Eigen::Matrix<T, 4, 1>* rhs, Eigen::Matrix<T, 4, 1>* solution, bool* success) { - CHECK(matrix != nullptr); - CHECK(rhs != nullptr); - CHECK(solution != nullptr); + ABSL_CHECK(matrix != nullptr); + ABSL_CHECK(rhs != nullptr); + ABSL_CHECK(solution != nullptr); *matrix = Eigen::Matrix<T, 4, 4>::Zero(); *rhs = Eigen::Matrix<T, 4, 1>::Zero(); @@ -3354,7 +3353,7 @@ RegionFlowFeatureList* feature_list, const EstimateModelOptions& model_options, float avg_camera_motion, InlierMask* inlier_mask, LinearSimilarityModel* best_model) const { - CHECK(best_model != nullptr); + ABSL_CHECK(best_model != nullptr); const int num_features = feature_list->feature_size(); if (!num_features) { @@ -3485,8 +3484,8 @@ void MotionEstimation::ComputeSimilarityInliers( const RegionFlowFeatureList& feature_list, int* num_inliers, int* num_strict_inliers) const { - CHECK(num_inliers); - CHECK(num_strict_inliers); + ABSL_CHECK(num_inliers); + ABSL_CHECK(num_strict_inliers); const auto& similarity_bounds = options_.stable_similarity_bounds(); @@ -3761,8 +3760,8 @@ float perspective_regularizer, Eigen::Matrix<T, Eigen::Dynamic, 8>* matrix, // tmp matrix Eigen::Matrix<T, 8, 1>* solution) { - CHECK(matrix); - CHECK(solution); + ABSL_CHECK(matrix); + ABSL_CHECK(solution); ABSL_CHECK_EQ(8, matrix->cols()); const int num_rows = 2 * feature_list.feature_size() + (perspective_regularizer == 0 ? 0 : 1); @@ -3850,9 +3849,9 @@ float perspective_regularizer, Eigen::Matrix<T, 8, 8>* matrix, Eigen::Matrix<T, 8, 1>* rhs, Eigen::Matrix<T, 8, 1>* solution, bool* success) { - CHECK(matrix != nullptr); - CHECK(rhs != nullptr); - CHECK(solution != nullptr); + ABSL_CHECK(matrix != nullptr); + ABSL_CHECK(rhs != nullptr); + ABSL_CHECK(solution != nullptr); *matrix = Eigen::Matrix<T, 8, 8>::Zero(); *rhs = Eigen::Matrix<T, 8, 1>::Zero(); @@ -4056,8 +4055,8 @@ const MixtureRowWeights& row_weights, float regularizer_lambda, Eigen::MatrixXf* matrix, // least squares matrix Eigen::MatrixXf* solution) { - CHECK(matrix); - CHECK(solution); + ABSL_CHECK(matrix); + ABSL_CHECK(solution); // cv::solve can hang for really bad conditioned systems. const double feature_irls_sum = RegionFlowFeatureIRLSSum(feature_list); @@ -4070,8 +4069,7 @@ ABSL_CHECK_EQ(matrix->cols(), num_dof); // 2 Rows (x,y) per feature. - ABSL_CHECK_EQ(matrix->rows(), - 2 * feature_list.feature_size() + num_constraints); + ABSL_CHECK_EQ(matrix->rows(), 2 * feature_list.feature_size() + num_constraints); ABSL_CHECK_EQ(solution->cols(), 1); ABSL_CHECK_EQ(solution->rows(), num_dof); @@ -4153,8 +4151,8 @@ const MixtureRowWeights& row_weights, float regularizer_lambda, Eigen::MatrixXf* matrix, // least squares matrix Eigen::MatrixXf* solution) { - CHECK(matrix); - CHECK(solution); + ABSL_CHECK(matrix); + ABSL_CHECK(solution); // cv::solve can hang for really bad conditioned systems. const double feature_irls_sum = RegionFlowFeatureIRLSSum(feature_list); @@ -4167,8 +4165,7 @@ ABSL_CHECK_EQ(matrix->cols(), num_dof); // 2 Rows (x,y) per feature. - ABSL_CHECK_EQ(matrix->rows(), - 2 * feature_list.feature_size() + num_constraints); + ABSL_CHECK_EQ(matrix->rows(), 2 * feature_list.feature_size() + num_constraints); ABSL_CHECK_EQ(solution->cols(), 1); ABSL_CHECK_EQ(solution->rows(), num_dof); @@ -4253,8 +4250,8 @@ const MixtureRowWeights& row_weights, float regularizer_lambda, Eigen::MatrixXf* matrix, // least squares matrix Eigen::MatrixXf* solution) { - CHECK(matrix); - CHECK(solution); + ABSL_CHECK(matrix); + ABSL_CHECK(solution); // cv::solve can hang for really bad conditioned systems. const double feature_irls_sum = RegionFlowFeatureIRLSSum(feature_list); @@ -4267,8 +4264,7 @@ ABSL_CHECK_EQ(matrix->cols(), num_dof); // 2 Rows (x,y) per feature. - ABSL_CHECK_EQ(matrix->rows(), - 2 * feature_list.feature_size() + num_constraints); + ABSL_CHECK_EQ(matrix->rows(), 2 * feature_list.feature_size() + num_constraints); ABSL_CHECK_EQ(solution->cols(), 1); ABSL_CHECK_EQ(solution->rows(), num_dof); @@ -4354,7 +4350,7 @@ void MotionEstimation::GetHomographyIRLSCenterWeights( const RegionFlowFeatureList& feature_list, std::vector<float>* weights) const { - CHECK(weights != nullptr); + ABSL_CHECK(weights != nullptr); const int num_features = feature_list.feature_size(); weights->clear(); @@ -4441,7 +4437,7 @@ void MotionEstimation::CheckTranslationAcceleration( std::vector<CameraMotion>* camera_motions) const { - CHECK(camera_motions != nullptr); + ABSL_CHECK(camera_motions != nullptr); std::vector<float> magnitudes; for (const auto& motion : *camera_motions) { const float translation_magnitude = @@ -4663,7 +4659,7 @@ float MotionEstimation::GridCoverage( const RegionFlowFeatureList& feature_list, float min_inlier_score, MotionEstimationThreadStorage* thread_storage) const { - CHECK(thread_storage != nullptr); + ABSL_CHECK(thread_storage != nullptr); // 10x10 grid for coverage estimation. const int grid_size = options_.coverage_grid_size(); @@ -4966,13 +4962,13 @@ } else { bool success = false; if (options_.use_highest_accuracy_for_normal_equations()) { - CHECK(!use_float); + ABSL_CHECK(!use_float); norm_model = HomographyL2NormalEquationSolve<double>( *feature_list, prev_solution, options_.homography_perspective_regularizer(), &matrix_d, &rhs_d, &solution_d, &success); } else { - CHECK(use_float); + ABSL_CHECK(use_float); norm_model = HomographyL2NormalEquationSolve<float>( *feature_list, prev_solution, options_.homography_perspective_regularizer(), &matrix_f, &rhs_f, @@ -5092,7 +5088,7 @@ // Compute weights if necessary. // Compute scale to index mixture weights from normalization. - CHECK(row_weights_.get() != nullptr); + ABSL_CHECK(row_weights_.get() != nullptr); ABSL_CHECK_EQ(row_weights_->YScale(), frame_height_ / normalized_domain_.y()); ABSL_CHECK_EQ(row_weights_->NumModels(), num_mixtures); @@ -5444,8 +5440,8 @@ void MotionEstimation::DetermineOverlayIndices( bool irls_weights_preinitialized, std::vector<CameraMotion>* camera_motions, std::vector<RegionFlowFeatureList*>* feature_lists) const { - CHECK(camera_motions != nullptr); - CHECK(feature_lists != nullptr); + ABSL_CHECK(camera_motions != nullptr); + ABSL_CHECK(feature_lists != nullptr); // Two stage estimation: First translation only, followed by // overlay analysis. const int num_frames = feature_lists->size(); @@ -5524,8 +5520,8 @@ const std::vector<TranslationModel>& translations, std::vector<RegionFlowFeatureList*>* feature_lists, std::vector<int>* overlay_indices) const { - CHECK(feature_lists != nullptr); - CHECK(overlay_indices != nullptr); + ABSL_CHECK(feature_lists != nullptr); + ABSL_CHECK(overlay_indices != nullptr); ABSL_CHECK_EQ(feature_lists->size(), translations.size()); overlay_indices->clear(); @@ -5614,7 +5610,7 @@ void MotionEstimation::PostIRLSSmoothing( const std::vector<CameraMotion>& camera_motions, std::vector<RegionFlowFeatureList*>* feature_lists) const { - CHECK(feature_lists != nullptr); + ABSL_CHECK(feature_lists != nullptr); std::vector<FeatureGrid<RegionFlowFeature>> feature_grids; std::vector<std::vector<int>> feature_taps_3; @@ -5694,7 +5690,7 @@ float grid_scale, int grid_dim_x, RegionFlowFeatureView* curr_view, RegionFlowFeatureView* prev_view) { - CHECK(curr_view != nullptr); + ABSL_CHECK(curr_view != nullptr); // Spatial filtering of inverse irls weights and the temporally weighted // pushed result from the next frame. for (auto& feature : *curr_view) { @@ -5852,7 +5848,7 @@ void MotionEstimation::InitGaussLUT(float sigma, float max_range, std::vector<float>* lut, float* scale) const { - CHECK(lut); + ABSL_CHECK(lut); // Calculate number of bins if scale is non-zero, otherwise use one bin per // integer in the domain [0, max_range]. const int lut_bins = (scale != nullptr) ? (1 << 10) : std::ceil(max_range);
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/motion_models.cc b/third_party/mediapipe/src/mediapipe/util/tracking/motion_models.cc index 5d6645f..2d81a3b 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/motion_models.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/motion_models.cc
@@ -22,8 +22,8 @@ #include "Eigen/Core" #include "Eigen/Dense" -#include "absl/log/absl_check.h" #include "absl/strings/str_format.h" +#include "absl/log/absl_check.h" // Set to true to use catmull rom mixture weights instead of Gaussian weights // for homography mixture estimation. @@ -64,7 +64,7 @@ void ModelAdapter<TranslationModel>::GetJacobianAtPoint(const Vector2_f& pt, float* jacobian) { - DCHECK(jacobian); + ABSL_DCHECK(jacobian); jacobian[0] = 1; jacobian[1] = 0; jacobian[2] = 0; @@ -116,7 +116,7 @@ SimilarityModel ModelAdapter<SimilarityModel>::FromFloatPointer( const float* args, bool identity_parametrization) { - DCHECK(args); + ABSL_DCHECK(args); SimilarityModel model; model.set_dx(args[0]); model.set_dy(args[1]); @@ -127,7 +127,7 @@ SimilarityModel ModelAdapter<SimilarityModel>::FromDoublePointer( const double* args, bool identity_parametrization) { - DCHECK(args); + ABSL_DCHECK(args); SimilarityModel model; model.set_dx(args[0]); model.set_dy(args[1]); @@ -314,7 +314,7 @@ void ModelAdapter<LinearSimilarityModel>::GetJacobianAtPoint( const Vector2_f& pt, float* jacobian) { - DCHECK(jacobian); + ABSL_DCHECK(jacobian); // First row. jacobian[0] = 1; jacobian[1] = 0; @@ -412,7 +412,7 @@ void ModelAdapter<AffineModel>::GetJacobianAtPoint(const Vector2_f& pt, float* jacobian) { - DCHECK(jacobian); + ABSL_DCHECK(jacobian); // First row. jacobian[0] = 1; jacobian[1] = 0; @@ -605,7 +605,7 @@ void ModelAdapter<Homography>::GetJacobianAtPoint(const Vector2_f& pt, float* jacobian) { - DCHECK(jacobian); + ABSL_DCHECK(jacobian); // First row. jacobian[0] = pt.x(); jacobian[1] = pt.y();
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/motion_models.h b/third_party/mediapipe/src/mediapipe/util/tracking/motion_models.h index 4edf28a..0392893 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/motion_models.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/motion_models.h
@@ -21,13 +21,13 @@ #include <vector> #include "absl/container/node_hash_map.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/singleton.h" #include "mediapipe/framework/port/vector.h" #include "mediapipe/util/tracking/camera_motion.pb.h" #include "mediapipe/util/tracking/motion_models.pb.h" #include "mediapipe/util/tracking/region_flow.pb.h" // NOLINT +#include "absl/log/absl_check.h" namespace mediapipe { @@ -867,7 +867,7 @@ template <class Model> void SmoothModels(const Model& sigma_time_model, const Model* model_sigma, std::vector<Model>* models) { - CHECK(models); + ABSL_CHECK(models); const int num_models = models->size(); @@ -967,7 +967,7 @@ inline TranslationModel ModelAdapter<TranslationModel>::FromFloatPointer( const float* args, bool) { - DCHECK(args); + ABSL_DCHECK(args); TranslationModel model; model.set_dx(args[0]); model.set_dy(args[1]); @@ -976,7 +976,7 @@ inline TranslationModel ModelAdapter<TranslationModel>::FromDoublePointer( const double* args, bool) { - DCHECK(args); + ABSL_DCHECK(args); TranslationModel model; model.set_dx(args[0]); model.set_dy(args[1]); @@ -1056,7 +1056,7 @@ inline LinearSimilarityModel ModelAdapter<LinearSimilarityModel>::FromFloatPointer( const float* args, bool identity_parametrization) { - DCHECK(args); + ABSL_DCHECK(args); LinearSimilarityModel model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_dx(args[0]); @@ -1069,7 +1069,7 @@ inline LinearSimilarityModel ModelAdapter<LinearSimilarityModel>::FromDoublePointer( const double* args, bool identity_parametrization) { - DCHECK(args); + ABSL_DCHECK(args); LinearSimilarityModel model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_dx(args[0]); @@ -1182,7 +1182,7 @@ inline AffineModel ModelAdapter<AffineModel>::FromFloatPointer( const float* args, bool identity_parametrization) { - DCHECK(args); + ABSL_DCHECK(args); AffineModel model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_dx(args[0]); @@ -1196,7 +1196,7 @@ inline AffineModel ModelAdapter<AffineModel>::FromDoublePointer( const double* args, bool identity_parametrization) { - DCHECK(args); + ABSL_DCHECK(args); AffineModel model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_dx(args[0]); @@ -1325,7 +1325,7 @@ inline Homography ModelAdapter<Homography>::FromFloatPointer( const float* args, bool identity_parametrization) { - DCHECK(args); + ABSL_DCHECK(args); Homography model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_h_00(id_shift + args[0]); @@ -1341,7 +1341,7 @@ inline Homography ModelAdapter<Homography>::FromDoublePointer( const double* args, bool identity_parametrization) { - DCHECK(args); + ABSL_DCHECK(args); Homography model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_h_00(id_shift + args[0]);
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/motion_models_cv.cc b/third_party/mediapipe/src/mediapipe/util/tracking/motion_models_cv.cc index b9b428a..0ff2f1e 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/motion_models_cv.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/motion_models_cv.cc
@@ -13,6 +13,7 @@ // limitations under the License. #include "mediapipe/util/tracking/motion_models_cv.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -41,7 +42,7 @@ void ModelCvConvert<Homography>::ToCvMat(const Homography& model, cv::Mat* matrix) { - CHECK(matrix != nullptr); + ABSL_CHECK(matrix != nullptr); matrix->create(3, 3, CV_32FC1); matrix->at<float>(0, 0) = model.h_00(); matrix->at<float>(0, 1) = model.h_01();
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/motion_saliency.cc b/third_party/mediapipe/src/mediapipe/util/tracking/motion_saliency.cc index 9e2911e..0e318360 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/motion_saliency.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/motion_saliency.cc
@@ -24,12 +24,12 @@ #include <memory> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/util/tracking/camera_motion.h" #include "mediapipe/util/tracking/measure_time.h" #include "mediapipe/util/tracking/region_flow.h" #include "mediapipe/util/tracking/region_flow.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -45,7 +45,7 @@ const RegionFlowFeatureList& feature_list, std::vector<float>* irls_weights, // optional. SalientPointFrame* salient_frame) { - CHECK(salient_frame); + ABSL_CHECK(salient_frame); ABSL_CHECK_EQ(frame_width_, feature_list.frame_width()); ABSL_CHECK_EQ(frame_height_, feature_list.frame_height()); @@ -106,7 +106,7 @@ const std::vector<float>* weights, SalientPointFrame* salient_frame) { // TODO: Handle vectors of size zero. - CHECK(salient_frame); + ABSL_CHECK(salient_frame); ABSL_CHECK_EQ(points->size(), weights->size()); float max_weight = *std::max_element(weights->begin(), weights->end()); @@ -213,7 +213,7 @@ void MotionSaliency::FilterMotionSaliency( std::vector<SalientPointFrame*>* saliency_point_list) { - CHECK(saliency_point_list != nullptr); + ABSL_CHECK(saliency_point_list != nullptr); const float sigma_time = options_.filtering_sigma_time(); const float sigma_space = options_.filtering_sigma_space(); @@ -330,7 +330,7 @@ void MotionSaliency::CollapseMotionSaliency( const SaliencyPointList& input_saliency, const Vector4_f& bounds, SaliencyPointList* output_saliency) { - CHECK(output_saliency); + ABSL_CHECK(output_saliency); output_saliency->clear(); output_saliency->resize(input_saliency.size()); @@ -379,8 +379,8 @@ const std::vector<float>& space_lut, float space_scale, std::vector<std::list<FeatureMode>>* mode_grid, std::vector<FeatureMode*>* mode_ptrs) { - CHECK(mode_grid); - CHECK(mode_ptrs); + ABSL_CHECK(mode_grid); + ABSL_CHECK(mode_ptrs); const int num_features = features.size(); mode_ptrs->reserve(num_features); @@ -440,8 +440,8 @@ void MotionSaliency::SalientModeFinding(std::vector<SalientLocation>* locations, std::vector<SalientMode>* modes) { - CHECK(modes); - CHECK(locations); + ABSL_CHECK(modes); + ABSL_CHECK(locations); if (locations->empty()) { return; } @@ -623,7 +623,7 @@ // mode finding and scales each point based on frame size. void MotionSaliency::DetermineSalientFrame( std::vector<SalientLocation> locations, SalientPointFrame* salient_frame) { - CHECK(salient_frame); + ABSL_CHECK(salient_frame); std::vector<SalientMode> modes; { @@ -661,7 +661,7 @@ float foreground_gamma, const CameraMotion* camera_motion, std::vector<float>* weights) { - CHECK(weights != nullptr); + ABSL_CHECK(weights != nullptr); weights->clear(); constexpr float kEpsilon = 1e-4f;
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/parallel_invoker.h b/third_party/mediapipe/src/mediapipe/util/tracking/parallel_invoker.h index 734e990..98abf57 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/parallel_invoker.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/parallel_invoker.h
@@ -71,7 +71,6 @@ #include <memory> -#include "absl/log/absl_check.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/port/logging.h" @@ -81,6 +80,7 @@ #ifdef __APPLE__ #include <dispatch/dispatch.h> #include <stdatomic.h> +#include "absl/log/absl_check.h" #endif #endif // PARALLEL_INVOKER_ACTIVE @@ -285,8 +285,7 @@ ABSL_CHECK_LT(flags_parallel_invoker_mode, PARALLEL_INVOKER_MAX_VALUE) << "Invalid invoker mode specified."; - ABSL_CHECK_GE(flags_parallel_invoker_mode, 0) - << "Invalid invoker mode specified."; + ABSL_CHECK_GE(flags_parallel_invoker_mode, 0) << "Invalid invoker mode specified."; } // Performs parallel iteration from [start to end), scheduling grain_size
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/push_pull_filtering.h b/third_party/mediapipe/src/mediapipe/util/tracking/push_pull_filtering.h index cdf451f..904538a5 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/push_pull_filtering.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/push_pull_filtering.h
@@ -33,10 +33,10 @@ #include <string> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/opencv_core_inc.h" #include "mediapipe/util/tracking/image_util.h" #include "mediapipe/util/tracking/push_pull_filtering.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -447,7 +447,7 @@ void PushPullFiltering<C, FilterWeightMultiplier>::AllocatePyramid( const cv::Size& domain_size, int border, int type, bool allocate_base_level, std::vector<cv::Mat>* pyramid) { - CHECK(pyramid != nullptr); + ABSL_CHECK(pyramid != nullptr); pyramid->clear(); pyramid->reserve(16); // Do not anticipate videos with dimensions // larger than 2^16. @@ -469,7 +469,7 @@ template <int C, class FilterWeightMultiplier> void PushPullFiltering<C, FilterWeightMultiplier>::InitializeImagePyramid( const cv::Mat& input_frame, std::vector<cv::Mat>* pyramid) { - CHECK(pyramid != nullptr); + ABSL_CHECK(pyramid != nullptr); ABSL_CHECK_GT(pyramid->size(), 0); cv::Mat base_level((*pyramid)[0], @@ -745,7 +745,7 @@ const std::vector<float>* data_weights, const cv::Mat* input_frame, cv::Mat* results) { ABSL_CHECK_EQ(data_locations.size(), data_values.size()); - CHECK(results != nullptr); + ABSL_CHECK(results != nullptr); if (data_weights) { ABSL_CHECK_EQ(data_weights->size(), data_locations.size()); @@ -807,7 +807,7 @@ int readout_level, // Default: 0. const cv::Mat* input_frame, // Optional. cv::Mat* results) { - CHECK(results != nullptr); + ABSL_CHECK(results != nullptr); // Create mip-map view (concat displacements with downsample_pyramid). std::vector<cv::Mat*> mip_map(PyramidLevels()); @@ -885,7 +885,7 @@ } if (use_bilateral_) { - CHECK(input_frame != nullptr); + ABSL_CHECK(input_frame != nullptr); InitializeImagePyramid(*input_frame, &input_frame_pyramid_); }
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/region_flow.cc b/third_party/mediapipe/src/mediapipe/util/tracking/region_flow.cc index 1babddd..cfffdb01 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/region_flow.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/region_flow.cc
@@ -22,11 +22,11 @@ #include "absl/container/node_hash_map.h" #include "absl/container/node_hash_set.h" -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/util/tracking/measure_time.h" #include "mediapipe/util/tracking/parallel_invoker.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -48,7 +48,7 @@ void GetRegionFlowFeatureList(const RegionFlowFrame& region_flow_frame, int distance_from_border, RegionFlowFeatureList* flow_feature_list) { - CHECK(flow_feature_list); + ABSL_CHECK(flow_feature_list); flow_feature_list->clear_feature(); const int frame_width = region_flow_frame.frame_width(); const int frame_height = region_flow_frame.frame_height(); @@ -119,7 +119,7 @@ void ComputeRegionFlowFeatureTexturedness( const RegionFlowFeatureList& flow_feature_list, bool use_15percent_as_max, std::vector<float>* texturedness) { - CHECK(texturedness != nullptr); + ABSL_CHECK(texturedness != nullptr); *texturedness = std::vector<float>(flow_feature_list.feature_size(), 1.0f); int texture_idx = 0; @@ -201,7 +201,7 @@ void GetRegionFlowFeatureIRLSWeights( const RegionFlowFeatureList& flow_feature_list, std::vector<float>* irls_weights) { - CHECK(irls_weights != nullptr); + ABSL_CHECK(irls_weights != nullptr); irls_weights->clear(); irls_weights->reserve(flow_feature_list.feature_size()); for (auto feature = flow_feature_list.feature().begin(); @@ -212,7 +212,7 @@ void SetRegionFlowFeatureIRLSWeights(const std::vector<float>& irls_weights, RegionFlowFeatureList* flow_feature_list) { - CHECK(flow_feature_list != nullptr); + ABSL_CHECK(flow_feature_list != nullptr); ABSL_CHECK_EQ(irls_weights.size(), flow_feature_list->feature_size()); int idx = 0; for (auto feature = flow_feature_list->mutable_feature()->begin(); @@ -285,7 +285,7 @@ void InvertRegionFlow(const RegionFlowFrame& region_flow_frame, RegionFlowFrame* inverted_flow_frame) { - CHECK(inverted_flow_frame); + ABSL_CHECK(inverted_flow_frame); inverted_flow_frame->CopyFrom(region_flow_frame); for (auto& region_flow : *inverted_flow_frame->mutable_region_flow()) { region_flow.set_centroid_x(region_flow.centroid_x() + region_flow.flow_x()); @@ -304,7 +304,7 @@ void InvertRegionFlowFeatureList(const RegionFlowFeatureList& feature_list, RegionFlowFeatureList* inverted_feature_list) { - CHECK(inverted_feature_list); + ABSL_CHECK(inverted_feature_list); *inverted_feature_list = feature_list; for (auto& feature : *inverted_feature_list->mutable_feature()) { InvertRegionFlowFeature(&feature); @@ -372,7 +372,7 @@ void ScaleSaliencyList(float scale, bool normalize_to_scale, SaliencyPointList* saliency_list) { - CHECK(saliency_list != nullptr); + ABSL_CHECK(saliency_list != nullptr); for (auto& point_frame : *saliency_list) { ScaleSalientPointFrame(scale, normalize_to_scale, &point_frame); } @@ -380,7 +380,7 @@ void ScaleSalientPointFrame(float scale, bool normalize_to_scale, SalientPointFrame* saliency) { - CHECK(saliency != nullptr); + ABSL_CHECK(saliency != nullptr); float saliency_scale = scale; if (normalize_to_scale) { float weight_sum = 0.0f; @@ -400,7 +400,7 @@ void ResetSaliencyBounds(float left, float bottom, float right, float top, SaliencyPointList* saliency_list) { - CHECK(saliency_list != nullptr); + ABSL_CHECK(saliency_list != nullptr); for (auto& point_frame : *saliency_list) { for (auto& salient_point : *point_frame.mutable_point()) { salient_point.set_left(left); @@ -413,8 +413,8 @@ bool EllipseFromCovariance(float a, float bc, float d, Vector2_f* axis_magnitude, float* angle) { - CHECK(axis_magnitude != nullptr); - CHECK(angle != nullptr); + ABSL_CHECK(axis_magnitude != nullptr); + ABSL_CHECK(angle != nullptr); // Get trace and determinant const float trace = a + d; @@ -476,7 +476,7 @@ void BoundingBoxFromEllipse(const Vector2_f& center, float norm_major_axis, float norm_minor_axis, float angle, std::vector<Vector2_f>* bounding_box) { - CHECK(bounding_box != nullptr); + ABSL_CHECK(bounding_box != nullptr); float dim_x; float dim_y; if (angle < M_PI * 0.25 || angle > M_PI * 0.75) { @@ -502,8 +502,8 @@ void CopyToEmptyFeatureList(RegionFlowFeatureList* src, RegionFlowFeatureList* dst) { - CHECK(src != nullptr); - CHECK(dst != nullptr); + ABSL_CHECK(src != nullptr); + ABSL_CHECK(dst != nullptr); // Swap out features for empty list. RegionFlowFeatureList empty_list; @@ -524,10 +524,10 @@ std::function<Vector2_f(const RegionFlowFeature&)> to_location_eval, RegionFlowFeatureList* from, RegionFlowFeatureList* result, std::vector<int>* source_indices) { - CHECK(from != nullptr); - CHECK(result != nullptr); - CHECK(from->long_tracks()) << "Intersection only works for long features"; - CHECK(to.long_tracks()) << "Intersection only works for long features"; + ABSL_CHECK(from != nullptr); + ABSL_CHECK(result != nullptr); + ABSL_CHECK(from->long_tracks()) << "Intersection only works for long features"; + ABSL_CHECK(to.long_tracks()) << "Intersection only works for long features"; // Hash features in to, based on track_id. absl::node_hash_map<int, const RegionFlowFeature*> track_map; @@ -594,7 +594,7 @@ present_tracks.insert(feature.track_id()); if (check_connectivity) { // A new feature should never have been erased before. - CHECK(old_ids_.find(feature.track_id()) == old_ids_.end()) + ABSL_CHECK(old_ids_.find(feature.track_id()) == old_ids_.end()) << "Feature : " << feature.track_id() << "was already removed."; } @@ -609,9 +609,9 @@ // Track is present, add to it. if (check_connectivity) { ABSL_CHECK_LT((FeatureLocation(find_pos->second.back()) - - FeatureMatchLocation(feature)) - .Norm2(), - 1e-4); + FeatureMatchLocation(feature)) + .Norm2(), + 1e-4); } find_pos->second.push_back(feature); } else { @@ -639,7 +639,7 @@ const std::vector<RegionFlowFeature>& features, std::vector<Vector2_f>* result, std::vector<float>* irls_weight, std::vector<Vector2_f>* flow) const { - CHECK(result != nullptr); + ABSL_CHECK(result != nullptr); if (features.empty()) { return; } @@ -731,7 +731,7 @@ void LongFeatureInfo::TrackLengths(const RegionFlowFeatureList& feature_list, std::vector<int>* track_lengths) const { - CHECK(track_lengths); + ABSL_CHECK(track_lengths); const int feature_size = feature_list.feature_size(); track_lengths->resize(feature_size); for (int k = 0; k < feature_size; ++k) { @@ -776,7 +776,7 @@ void GridTaps(int dim_x, int dim_y, int tap_radius, std::vector<std::vector<int>>* taps) { - CHECK(taps); + ABSL_CHECK(taps); const int grid_size = dim_x * dim_y; const int diam = 2 * tap_radius + 1; taps->resize(grid_size);
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/region_flow.h b/third_party/mediapipe/src/mediapipe/util/tracking/region_flow.h index db70a2b..3a2fb21 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/region_flow.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/region_flow.h
@@ -24,11 +24,11 @@ #include <utility> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/vector.h" #include "mediapipe/util/tracking/motion_models.h" #include "mediapipe/util/tracking/region_flow.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -235,7 +235,7 @@ inline void RegionFlowFeatureListViaTransform( const MixtureHomography& mix, RegionFlowFeatureList* flow_feature_list, float a, float b, bool set_match, const MixtureRowWeights* row_weights) { - CHECK(row_weights) << "Row weights required for mixtures."; + ABSL_CHECK(row_weights) << "Row weights required for mixtures."; for (auto& feature : *flow_feature_list->mutable_feature()) { const float* weights = row_weights->RowWeights(feature.y()); @@ -276,7 +276,7 @@ template <class Predicate> int FilterRegionFlowFeatureList(const Predicate& predicate, float reset_value, RegionFlowFeatureList* flow_feature_list) { - CHECK(flow_feature_list != nullptr); + ABSL_CHECK(flow_feature_list != nullptr); int num_passing_features = 0; for (auto& feature : *flow_feature_list->mutable_feature()) { std::pair<float, bool> filter_result = @@ -297,7 +297,7 @@ float reset_value, const RegionFlowFeatureList& feature_list, std::vector<float>* result_weights) { - CHECK(result_weights != nullptr); + ABSL_CHECK(result_weights != nullptr); result_weights->clear(); int num_passing_features = 0; @@ -319,8 +319,8 @@ void SelectFeaturesFromList(const Predicate& predicate, RegionFlowFeatureList* feature_list, RegionFlowFeatureView* feature_view) { - CHECK(feature_list != nullptr); - CHECK(feature_view != nullptr); + ABSL_CHECK(feature_list != nullptr); + ABSL_CHECK(feature_view != nullptr); for (auto& feature : *feature_list->mutable_feature()) { if (predicate(feature)) { feature_view->push_back(&feature); @@ -330,8 +330,8 @@ inline void SelectAllFeaturesFromList(RegionFlowFeatureList* feature_list, RegionFlowFeatureView* feature_view) { - CHECK(feature_list != nullptr); - CHECK(feature_view != nullptr); + ABSL_CHECK(feature_list != nullptr); + ABSL_CHECK(feature_view != nullptr); for (auto& feature : *feature_list->mutable_feature()) { feature_view->push_back(&feature); } @@ -343,7 +343,7 @@ template <class Predicate> void SortRegionFlowFeatureView(const Predicate& predicate, RegionFlowFeatureView* feature_view) { - CHECK(feature_view != nullptr); + ABSL_CHECK(feature_view != nullptr); std::sort(feature_view->begin(), feature_view->end(), predicate); } @@ -591,7 +591,7 @@ std::vector<std::vector<int>>* feature_taps_5, // Optional. Vector2_i* num_grid_bins, // Optional. std::vector<FeatureGrid<Feature>>* feature_grids) { - CHECK(feature_grids); + ABSL_CHECK(feature_grids); ABSL_CHECK_GT(grid_resolution, 0.0f); const int num_frames = feature_views.size();
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/region_flow_computation.cc b/third_party/mediapipe/src/mediapipe/util/tracking/region_flow_computation.cc index 2446e72..91018bb 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/region_flow_computation.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/region_flow_computation.cc
@@ -28,7 +28,6 @@ #include "Eigen/Core" #include "absl/container/flat_hash_map.h" #include "absl/container/node_hash_set.h" -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/opencv_core_inc.h" @@ -48,6 +47,7 @@ #include "mediapipe/util/tracking/tone_estimation.pb.h" #include "mediapipe/util/tracking/tone_models.h" #include "mediapipe/util/tracking/tone_models.pb.h" +#include "absl/log/absl_check.h" using std::max; using std::min; @@ -133,7 +133,7 @@ void GetPatchDescriptorAtPoint(const cv::Mat& rgb_frame, const Vector2_i& pt, const int radius, cv::Mat* lab_window, PatchDescriptor* descriptor) { - CHECK(descriptor); + ABSL_CHECK(descriptor); descriptor->clear_data(); // Reserve enough data for mean and upper triangular part of @@ -258,8 +258,7 @@ ABSL_CHECK_EQ(prev_rgb_frame->cols, cols); } - ABSL_CHECK_LE(patch_descriptor_radius, - flow_feature_list->distance_from_border()); + ABSL_CHECK_LE(patch_descriptor_radius, flow_feature_list->distance_from_border()); ParallelFor( 0, flow_feature_list->feature_size(), 1, @@ -474,7 +473,7 @@ // Stores grayscale square patch with length patch_size extracted at center in // image frame and stores result in patch. void ExtractPatch(const cv::Point2f& center, int patch_size, cv::Mat* patch) { - CHECK(patch != nullptr); + ABSL_CHECK(patch != nullptr); patch->create(patch_size, patch_size, CV_8UC1); cv::getRectSubPix(frame, cv::Size(patch_size, patch_size), center, *patch); } @@ -533,13 +532,13 @@ float MotionMagForId(int id) const { auto id_iter = track_info.find(id); - DCHECK(id_iter != track_info.end()); + ABSL_DCHECK(id_iter != track_info.end()); return id_iter->second.motion_mag; } void UpdateMotion(int id, float motion_mag) { auto id_iter = track_info.find(id); - DCHECK(id_iter != track_info.end()); + ABSL_DCHECK(id_iter != track_info.end()); if (id_iter->second.motion_mag >= 0) { id_iter->second.motion_mag = id_iter->second.motion_mag * 0.5f + 0.5f * motion_mag; @@ -619,7 +618,7 @@ } ABSL_CHECK_NE(options.tracking_options().output_flow_direction(), - TrackingOptions::CONSECUTIVELY) + TrackingOptions::CONSECUTIVELY) << "Output direction must be either set to FORWARD or BACKWARD."; use_downsampling_ = options_.downsample_mode() != RegionFlowComputationOptions::DOWNSAMPLE_NONE; @@ -758,7 +757,7 @@ break; } - CHECK(!options_.gain_correction() || !IsVerifyLongFeatures()) + ABSL_CHECK(!options_.gain_correction() || !IsVerifyLongFeatures()) << "Gain correction mode with verification of long features is not " << "supported."; @@ -872,16 +871,16 @@ bool compute_match_descriptor, const cv::Mat* curr_color_image, const cv::Mat* prev_color_image) { ABSL_CHECK_GT(region_flow_results_.size(), track_index); - CHECK(region_flow_results_[track_index].get()); + ABSL_CHECK(region_flow_results_[track_index].get()); std::unique_ptr<RegionFlowFeatureList> feature_list( std::move(region_flow_results_[track_index])); if (compute_feature_descriptor) { - CHECK(curr_color_image != nullptr); + ABSL_CHECK(curr_color_image != nullptr); ABSL_CHECK_EQ(3, curr_color_image->channels()); if (compute_match_descriptor) { - CHECK(prev_color_image != nullptr); + ABSL_CHECK(prev_color_image != nullptr); ABSL_CHECK_EQ(3, prev_color_image->channels()); } @@ -890,7 +889,7 @@ compute_match_descriptor ? prev_color_image : nullptr, options_.patch_descriptor_radius(), feature_list.get()); } else { - CHECK(!compute_match_descriptor) << "Set compute_feature_descriptor also " + ABSL_CHECK(!compute_match_descriptor) << "Set compute_feature_descriptor also " << "if setting compute_match_descriptor"; } @@ -1092,7 +1091,7 @@ if (!IsModelIdentity(initial_transform)) { ABSL_CHECK_EQ(1, frames_to_track_) << "Initial transform is not supported " - << "for multi frame tracking"; + << "for multi frame tracking"; Homography transform = initial_transform; if (downsample_scale_ != 1) { const float scale = 1.0f / downsample_scale_; @@ -1208,15 +1207,15 @@ cv::Mat RegionFlowComputation::GetGrayscaleFrameFromResults() { ABSL_CHECK_GT(data_queue_.size(), 0) << "Empty queue, was AddImage* called?"; FrameTrackingData* curr_data = data_queue_.back().get(); - CHECK(curr_data); + ABSL_CHECK(curr_data); return curr_data->frame; } void RegionFlowComputation::GetFeatureTrackInliers( bool skip_estimation, TrackedFeatureList* features, TrackedFeatureView* inliers) const { - CHECK(features != nullptr); - CHECK(inliers != nullptr); + ABSL_CHECK(features != nullptr); + ABSL_CHECK(inliers != nullptr); inliers->clear(); if (skip_estimation) { inliers->reserve(features->size()); @@ -1300,7 +1299,7 @@ bool track_features = true; bool force_feature_extraction_next_frame = false; if (options_.tracking_options().wide_baseline_matching()) { - CHECK(initial_transform == nullptr) + ABSL_CHECK(initial_transform == nullptr) << "Can't use wide baseline matching and initial transform as the " << "same time."; @@ -1642,9 +1641,9 @@ void RegionFlowComputation::AdaptiveGoodFeaturesToTrack( const std::vector<cv::Mat>& extraction_pyramid, int max_features, float mask_scale, cv::Mat* mask, FrameTrackingData* data) { - CHECK(data != nullptr); - CHECK(feature_tmp_image_1_.get() != nullptr); - CHECK(feature_tmp_image_2_.get() != nullptr); + ABSL_CHECK(data != nullptr); + ABSL_CHECK(feature_tmp_image_1_.get() != nullptr); + ABSL_CHECK(feature_tmp_image_2_.get() != nullptr); cv::Mat* eig_image = feature_tmp_image_1_.get(); cv::Mat* tmp_image = feature_tmp_image_2_.get(); @@ -1887,7 +1886,7 @@ AffineModel RegionFlowComputation::AffineModelFromFeatures( TrackedFeatureList* features) const { - CHECK(features != nullptr); + ABSL_CHECK(features != nullptr); // Downscaled domain as output. MotionEstimation motion_estimation(MotionEstimationOptions(), frame_width_, @@ -1910,7 +1909,7 @@ void RegionFlowComputation::ZeroMotionGridFeatures( int frame_width, int frame_height, float frac_grid_step_x, float frac_grid_step_y, RegionFlowFeatureList* result) { - CHECK(result != nullptr); + ABSL_CHECK(result != nullptr); result->Clear(); TrackedFeatureList features; @@ -1933,7 +1932,7 @@ void RegionFlowComputation::DenseZeroMotionSamples( int frame_width, int frame_height, float frac_diameter, float frac_steps_x, float frac_steps_y, RegionFlowFeatureList* result) { - CHECK(result != nullptr); + ABSL_CHECK(result != nullptr); // Ensure patch fits into frame. const int radius = @@ -1980,7 +1979,7 @@ float frac_grid_step_x, float frac_grid_step_y, TrackedFeatureList* results) { - CHECK(results); + ABSL_CHECK(results); auto& tracked_features = *results; tracked_features.clear(); @@ -2016,7 +2015,7 @@ float reference_mean, float input_mean, cv::Mat* calibrated_frame) const { - CHECK(calibrated_frame); + ABSL_CHECK(calibrated_frame); ABSL_CHECK_EQ(reference_frame.rows, input_frame.rows); ABSL_CHECK_EQ(reference_frame.cols, input_frame.cols); @@ -2182,7 +2181,7 @@ void RegionFlowComputation::RemoveAbsentFeatures( const TrackedFeatureList& prev_result, FrameTrackingData* data) { - CHECK(long_track_data_ != nullptr); + ABSL_CHECK(long_track_data_ != nullptr); // Build hash set of track ids. absl::node_hash_set<int> track_ids; @@ -2331,7 +2330,7 @@ if (prev_result) { // Seed feature mask and results with tracking ids. - CHECK(long_track_data_ != nullptr); + ABSL_CHECK(long_track_data_ != nullptr); const int max_track_length = options_.tracking_options().long_tracks_max_frames(); // Drop a feature with a propability X, such that all qualifying @@ -2360,7 +2359,7 @@ // position, for BACKWARD output flow, flow is inverted, so that feature // locations already point to locations in the current frame. ABSL_CHECK_EQ(options_.tracking_options().internal_tracking_direction(), - TrackingOptions::FORWARD); + TrackingOptions::FORWARD); float match_sign = options_.tracking_options().output_flow_direction() == TrackingOptions::FORWARD ? 1.0f @@ -2558,7 +2557,7 @@ const int track_win_size = options_.tracking_options().tracking_window_size(); ABSL_CHECK_GT(track_win_size, 1) << "Needs to be at least 2 pixels in each " - << "direction"; + << "direction"; // Proceed with gain correction only if it succeeds, and set flag accordingly. bool frame1_gain_reference = true; @@ -2642,7 +2641,7 @@ // Init neighborhoods if needed. if (IsVerifyLongFeatures()) { // data1 should be initialized at this point. - CHECK(data1.neighborhoods != nullptr); + ABSL_CHECK(data1.neighborhoods != nullptr); if (data2.neighborhoods == nullptr) { data2.neighborhoods.reset(new std::vector<cv::Mat>()); data2.neighborhoods->resize(num_valid_features); @@ -2958,7 +2957,7 @@ ABSL_CHECK_LT(index2, data_queue_.size()); const FrameTrackingData& data1 = *data_queue_[index1]; FrameTrackingData* data2 = data_queue_[index2].get(); - CHECK(data1.source != nullptr); + ABSL_CHECK(data1.source != nullptr); if (!data1.features_initialized) { data2->features = data1.source->features; @@ -3140,7 +3139,7 @@ void RegionFlowComputation::DetermineRegionFlowInliers( const TrackedFeatureMap& region_feature_map, TrackedFeatureView* inliers) const { - CHECK(inliers); + ABSL_CHECK(inliers); inliers->clear(); // Run RANSAC on each region. @@ -3243,7 +3242,7 @@ total_features += region_features.size(); } - CHECK(!region_feature_map.empty()) + ABSL_CHECK(!region_feature_map.empty()) << "Empty grid passed. Check input dimensions"; const float threshold = @@ -3256,7 +3255,7 @@ void RegionFlowComputation::RegionFlowFeatureListToRegionFlow( const RegionFlowFeatureList& feature_list, RegionFlowFrame* frame) const { - CHECK(frame != nullptr); + ABSL_CHECK(frame != nullptr); frame->set_num_total_features(feature_list.feature_size()); frame->set_unstable_frame(feature_list.unstable());
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/region_flow_visualization.cc b/third_party/mediapipe/src/mediapipe/util/tracking/region_flow_visualization.cc index 841ce5e0..3094d66 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/region_flow_visualization.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/region_flow_visualization.cc
@@ -19,12 +19,12 @@ #include <memory> #include <numeric> -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/util/tracking/measure_time.h" #include "mediapipe/util/tracking/parallel_invoker.h" #include "mediapipe/util/tracking/region_flow.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -48,7 +48,7 @@ void VisualizeRegionFlow(const RegionFlowFrame& region_flow_frame, cv::Mat* output) { - CHECK(output); + ABSL_CHECK(output); VisualizeRegionFlowImpl(region_flow_frame, output); } @@ -119,7 +119,7 @@ const cv::Scalar& outlier, bool irls_visualization, float scale_x, float scale_y, cv::Mat* output) { - CHECK(output); + ABSL_CHECK(output); VisualizeRegionFlowFeaturesImpl(feature_list, color, outlier, irls_visualization, scale_x, scale_y, output); } @@ -187,7 +187,7 @@ const cv::Scalar& outlier, int min_track_length, int max_points_per_track, float scale_x, float scale_y, cv::Mat* output) { - CHECK(output); + ABSL_CHECK(output); VisualizeLongFeatureStreamImpl(stream, color, outlier, min_track_length, max_points_per_track, scale_x, scale_y,
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/streaming_buffer.cc b/third_party/mediapipe/src/mediapipe/util/tracking/streaming_buffer.cc index 286edbb..6bb081d 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/streaming_buffer.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/streaming_buffer.cc
@@ -14,8 +14,8 @@ #include "mediapipe/util/tracking/streaming_buffer.h" -#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -24,7 +24,7 @@ : overlap_(overlap) { ABSL_CHECK_GE(overlap, 0); for (auto& item : data_configuration) { - CHECK(data_config_.find(item.first) == data_config_.end()) + ABSL_CHECK(data_config_.find(item.first) == data_config_.end()) << "Tag " << item.first << " already exists"; data_config_[item.first] = item.second; // Init deque. @@ -46,7 +46,7 @@ } int StreamingBuffer::BufferSize(const std::string& tag) const { - CHECK(HasTag(tag)); + ABSL_CHECK(HasTag(tag)); return data_.find(tag)->second.size(); } @@ -120,7 +120,7 @@ } void StreamingBuffer::DiscardDatum(const std::string& tag, int num_frames) { - CHECK(HasTag(tag)); + ABSL_CHECK(HasTag(tag)); auto& queue = data_[tag]; if (queue.empty()) { return; @@ -131,7 +131,7 @@ void StreamingBuffer::DiscardDatumFromEnd(const std::string& tag, int num_frames) { - CHECK(HasTag(tag)); + ABSL_CHECK(HasTag(tag)); auto& queue = data_[tag]; if (queue.empty()) { return;
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/streaming_buffer.h b/third_party/mediapipe/src/mediapipe/util/tracking/streaming_buffer.h index bdaeb0c..365b2d0 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/streaming_buffer.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/streaming_buffer.h
@@ -23,10 +23,10 @@ #include <vector> #include "absl/container/node_hash_map.h" -#include "absl/log/absl_check.h" #include "absl/types/any.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/tool/type_util.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -78,7 +78,7 @@ // // Reached chunk boundary? // if (buffer_size == 100) { // // Check that we buffered one frame for each motion. -// CHECK(streaming_buffer.HaveEqualSize({"frame", "motion"})); +// ABSL_CHECK(streaming_buffer.HaveEqualSize({"frame", "motion"})); // // // Compute saliency. // for (int k = 0; k < 100; ++k) { @@ -297,7 +297,7 @@ // Terminates recursive template expansion for AddDataImpl. Will never be // called. void AddDataImpl(const std::vector<std::string>& tags) { - CHECK(tags.empty()); + ABSL_CHECK(tags.empty()); } private: @@ -324,7 +324,7 @@ template <class T> void StreamingBuffer::AddDatum(const std::string& tag, std::unique_ptr<T> pointer) { - CHECK(HasTag(tag)); + ABSL_CHECK(HasTag(tag)); ABSL_CHECK_EQ(data_config_[tag], kTypeId<PointerType<T>>.hash_code()); auto& buffer = data_[tag]; absl::any packet(PointerType<T>(CreatePointer(pointer.release()))); @@ -389,7 +389,7 @@ T* StreamingBuffer::GetMutableDatum(const std::string& tag, int frame_index) const { ABSL_CHECK_GE(frame_index, 0); - CHECK(HasTag(tag)); + ABSL_CHECK(HasTag(tag)); auto& buffer = data_.find(tag)->second; if (frame_index > buffer.size()) { return nullptr; @@ -441,12 +441,12 @@ template <class T> bool StreamingBuffer::IsInitialized(const std::string& tag) const { - CHECK(HasTag(tag)); + ABSL_CHECK(HasTag(tag)); const auto& buffer = data_.find(tag)->second; int idx = 0; for (const auto& item : buffer) { const PointerType<T>* pointer = absl::any_cast<const PointerType<T>>(&item); - CHECK(pointer != nullptr); + ABSL_CHECK(pointer != nullptr); if (*pointer == nullptr) { LOG(ERROR) << "Data for " << tag << " at frame " << idx << " is not initialized."; @@ -459,7 +459,7 @@ template <class T> std::vector<T*> StreamingBuffer::GetMutableDatumVector( const std::string& tag) const { - CHECK(HasTag(tag)); + ABSL_CHECK(HasTag(tag)); auto& buffer = data_.find(tag)->second; std::vector<T*> result; for (const auto& packet : buffer) { @@ -478,7 +478,7 @@ template <class T, class Functor> void StreamingBuffer::OutputDatum(bool flush, const std::string& tag, const Functor& functor) { - CHECK(HasTag(tag)); + ABSL_CHECK(HasTag(tag)); const int end_frame = MaxBufferSize() - (flush ? 0 : overlap_); for (int k = 0; k < end_frame; ++k) { functor(k, ReleaseDatum<T>(tag, k)); @@ -488,7 +488,7 @@ template <class T> std::unique_ptr<T> StreamingBuffer::ReleaseDatum(const std::string& tag, int frame_index) { - CHECK(HasTag(tag)); + ABSL_CHECK(HasTag(tag)); ABSL_CHECK_GE(frame_index, 0); auto& buffer = data_.find(tag)->second;
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/tone_estimation.cc b/third_party/mediapipe/src/mediapipe/util/tracking/tone_estimation.cc index 2fdfa11..8a59f20 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/tone_estimation.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/tone_estimation.cc
@@ -21,9 +21,9 @@ #include <numeric> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/util/tracking/motion_models.pb.h" #include "mediapipe/util/tracking/tone_models.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -83,7 +83,7 @@ ToneChange* tone_change, cv::Mat* debug_output) { ABSL_CHECK_EQ(original_height_, curr_frame_input.rows); ABSL_CHECK_EQ(original_width_, curr_frame_input.cols); - CHECK(tone_change != nullptr); + ABSL_CHECK(tone_change != nullptr); const cv::Mat& curr_frame = use_downsampling_ ? *resized_input_ : curr_frame_input; @@ -213,8 +213,8 @@ void ToneEstimation::EstimateGainBiasModel(int irls_iterations, ColorToneMatches* color_tone_matches, GainBiasModel* gain_bias_model) { - CHECK(color_tone_matches != nullptr); - CHECK(gain_bias_model != nullptr); + ABSL_CHECK(color_tone_matches != nullptr); + ABSL_CHECK(gain_bias_model != nullptr); // Effectively estimate each model independently. float solution_ptr[6] = {1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f};
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/tone_estimation.h b/third_party/mediapipe/src/mediapipe/util/tracking/tone_estimation.h index 9f37cdd3..f5c9ec1 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/tone_estimation.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/tone_estimation.h
@@ -25,7 +25,6 @@ #include <memory> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/opencv_core_inc.h" @@ -35,6 +34,7 @@ #include "mediapipe/util/tracking/region_flow.pb.h" #include "mediapipe/util/tracking/tone_estimation.pb.h" #include "mediapipe/util/tracking/tone_models.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -151,7 +151,7 @@ void ToneEstimation::ComputeClipMask(const ClipMaskOptions& options, const cv::Mat& frame, ClipMask<C>* clip_mask) { - CHECK(clip_mask != nullptr); + ABSL_CHECK(clip_mask != nullptr); ABSL_CHECK_EQ(frame.channels(), C); // Over / Underexposure handling. @@ -224,7 +224,7 @@ const ClipMask<C>& curr_clip_mask, // Optional. const ClipMask<C>& prev_clip_mask, // Optional. ColorToneMatches* color_tone_matches, cv::Mat* debug_output) { - CHECK(color_tone_matches != nullptr); + ABSL_CHECK(color_tone_matches != nullptr); ABSL_CHECK_EQ(curr_frame.channels(), C); ABSL_CHECK_EQ(prev_frame.channels(), C);
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/tone_models.cc b/third_party/mediapipe/src/mediapipe/util/tracking/tone_models.cc index 4ab896a..64999ee8 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/tone_models.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/tone_models.cc
@@ -16,8 +16,8 @@ #include <cmath> -#include "absl/log/absl_check.h" #include "absl/strings/str_format.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -48,7 +48,7 @@ bool normalized_model, const cv::Mat& input, cv::Mat* output) { - CHECK(output != nullptr); + ABSL_CHECK(output != nullptr); const int out_channels = output->channels(); ABSL_CHECK_EQ(input.channels(), 3);
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/tone_models.h b/third_party/mediapipe/src/mediapipe/util/tracking/tone_models.h index 17b4427..6f85844 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/tone_models.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/tone_models.h
@@ -23,12 +23,12 @@ #include <string> #include <vector> -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/opencv_core_inc.h" #include "mediapipe/framework/port/vector.h" #include "mediapipe/util/tracking/tone_models.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { // Abstract Adapter for tone models. @@ -247,7 +247,7 @@ template <class T> GainBiasModel ToneModelAdapter<GainBiasModel>::FromPointer(const T* args, bool identity) { - DCHECK(args); + ABSL_DCHECK(args); GainBiasModel model; const float id_shift = identity ? 1.0f : 0.0f; model.set_gain_c1(args[0] + id_shift); @@ -347,7 +347,7 @@ template <class T> AffineToneModel ToneModelAdapter<AffineToneModel>::FromPointer(const T* args, bool identity) { - DCHECK(args); + ABSL_DCHECK(args); AffineToneModel model; const float id_shift = identity ? 1.0f : 0.0f; model.set_g_00(args[0] + id_shift); @@ -370,7 +370,7 @@ template <class T> void ToneModelAdapter<AffineToneModel>::ToPointerPad( const AffineToneModel& model, bool pad_square, T* args) { - DCHECK(args); + ABSL_DCHECK(args); args[0] = model.g_00(); args[1] = model.g_01(); args[2] = model.g_02(); @@ -593,7 +593,7 @@ void ToneModelMethods<Model, Adapter>::MapImageIndependent( const Model& model, bool log_domain, bool normalized_model, const cv::Mat& input, cv::Mat* output) { - CHECK(output != nullptr); + ABSL_CHECK(output != nullptr); ABSL_CHECK_EQ(input.channels(), C); ABSL_CHECK_EQ(output->channels(), C);
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/tracking.cc b/third_party/mediapipe/src/mediapipe/util/tracking/tracking.cc index efac37d..afb7047 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/tracking.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/tracking.cc
@@ -25,7 +25,6 @@ #include "Eigen/Dense" #include "Eigen/SVD" #include "absl/algorithm/container.h" -#include "absl/log/absl_check.h" #include "absl/memory/memory.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/opencv_calib3d_inc.h" @@ -34,6 +33,7 @@ #include "mediapipe/util/tracking/flow_packager.pb.h" #include "mediapipe/util/tracking/measure_time.h" #include "mediapipe/util/tracking/motion_models.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -159,7 +159,7 @@ bool LinearSimilarityL2Solve( const std::vector<const MotionVector*>& motion_vectors, const std::vector<float>& weights, LinearSimilarityModel* model) { - CHECK(model); + ABSL_CHECK(model); if (motion_vectors.size() < 4) { LOG(ERROR) << "Requiring at least 4 input vectors for sufficient solve."; return false; @@ -235,7 +235,7 @@ // Taken from MotionEstimation::HomographyL2NormalEquationSolve bool HomographyL2Solve(const std::vector<const MotionVector*>& motion_vectors, const std::vector<float>& weights, Homography* model) { - CHECK(model); + ABSL_CHECK(model); cv::Mat matrix(8, 8, CV_32F); cv::Mat solution(8, 1, CV_32F); @@ -375,7 +375,7 @@ void TransformQuadInMotionBoxState(const MotionBoxState& curr_pos, const Homography& homography, MotionBoxState* next_pos) { - CHECK(next_pos != nullptr); + ABSL_CHECK(next_pos != nullptr); if (!curr_pos.has_pos_x() || !curr_pos.has_pos_y() || !curr_pos.has_width() || !curr_pos.has_height()) { LOG(ERROR) << "Previous box does not exist, cannot transform!"; @@ -647,7 +647,7 @@ bool MotionBoxLines(const MotionBoxState& state, const Vector2_f& scaling, std::array<Vector3_f, 4>* box_lines) { - CHECK(box_lines); + ABSL_CHECK(box_lines); std::array<Vector2_f, 4> corners = MotionBoxCorners(state, scaling); for (int k = 0; k < 4; ++k) { const Vector2_f diff = corners[(k + 1) % 4] - corners[k]; @@ -666,8 +666,8 @@ void MotionBoxBoundingBox(const MotionBoxState& state, Vector2_f* top_left, Vector2_f* bottom_right) { - CHECK(top_left); - CHECK(bottom_right); + ABSL_CHECK(top_left); + ABSL_CHECK(bottom_right); std::array<Vector2_f, 4> corners = MotionBoxCorners(state); @@ -688,7 +688,7 @@ void MotionBoxInlierLocations(const MotionBoxState& state, std::vector<Vector2_f>* inlier_pos) { - CHECK(inlier_pos); + ABSL_CHECK(inlier_pos); inlier_pos->clear(); for (int k = 0; k < state.inlier_id_match_pos_size(); k += 2) { inlier_pos->push_back( @@ -699,7 +699,7 @@ void MotionBoxOutlierLocations(const MotionBoxState& state, std::vector<Vector2_f>* outlier_pos) { - CHECK(outlier_pos); + ABSL_CHECK(outlier_pos); outlier_pos->clear(); for (int k = 0; k < state.outlier_id_match_pos_size(); k += 2) { outlier_pos->push_back( @@ -737,7 +737,7 @@ } void InitializeQuadInMotionBoxState(MotionBoxState* state) { - CHECK(state != nullptr); + ABSL_CHECK(state != nullptr); // Every quad has 4 vertices. Each vertex has x and y 2 coordinates. So // a total of 8 floating point values. if (state->quad().vertices_size() != 8) { @@ -1152,7 +1152,7 @@ update_pos->spatial_confidence().end()); ABSL_CHECK_EQ(old_confidence.size(), old_prior.size()); - CHECK(old_confidence.empty() || + ABSL_CHECK(old_confidence.empty() || grid_size * grid_size == old_confidence.size()) << "Empty or priors of constant size expected"; @@ -1282,9 +1282,9 @@ float aspect_ratio, float* expand_mag, Vector2_f* top_left, Vector2_f* bottom_right) const { - CHECK(top_left); - CHECK(bottom_right); - CHECK(expand_mag); + ABSL_CHECK(top_left); + ABSL_CHECK(bottom_right); + ABSL_CHECK(expand_mag); MotionBoxBoundingBox(curr_pos, top_left, bottom_right); @@ -1311,8 +1311,8 @@ const Vector2_f& inv_box_domain, float* spatial_gauss_x, float* spatial_gauss_y) const { - CHECK(spatial_gauss_x); - CHECK(spatial_gauss_y); + ABSL_CHECK(spatial_gauss_x); + ABSL_CHECK(spatial_gauss_y); // Space sigma depends on how much the tracked object fills the rectangle. // We get this information from the inlier extent of the previous @@ -1341,7 +1341,7 @@ const Vector2_f& bottom_right, const std::vector<const MotionVector*>& vectors, std::vector<Vector2_f>* grid_positions) { - CHECK(grid_positions); + ABSL_CHECK(grid_positions); // Slightly larger domain to avoid boundary issues. const Vector2_f inv_grid_domain( @@ -1474,8 +1474,8 @@ std::min(kMaxBoxCenterBlendWeight, current_state.prior_weight())); if (tracking_degrees_ == TrackStepOptions::TRACKING_DEGREE_OBJECT_PERSPECTIVE) { - CHECK(initial_state.has_quad()); - CHECK(current_state.has_quad()); + ABSL_CHECK(initial_state.has_quad()); + ABSL_CHECK(current_state.has_quad()); homography_ = ComputeHomographyFromQuad(current_state.quad(), initial_state.quad()); box_center_transformed_ = @@ -1562,10 +1562,10 @@ const std::vector<const MotionBoxState*>& history, std::vector<const MotionVector*>* vectors, std::vector<float>* weights, int* number_of_good_prior, int* number_of_cont_inliers) const { - CHECK(weights); - CHECK(vectors); - CHECK(number_of_good_prior); - CHECK(number_of_cont_inliers); + ABSL_CHECK(weights); + ABSL_CHECK(vectors); + ABSL_CHECK(number_of_good_prior); + ABSL_CHECK(number_of_cont_inliers); const int num_max_vectors = end_idx - start_idx; weights->clear(); @@ -1913,9 +1913,9 @@ const Vector2_f& irls_scale, std::vector<float>* weights, Vector2_f* object_translation, LinearSimilarityModel* object_similarity, Homography* object_homography) const { - CHECK(object_translation); - CHECK(object_similarity); - CHECK(object_homography); + ABSL_CHECK(object_translation); + ABSL_CHECK(object_similarity); + ABSL_CHECK(object_homography); const int num_vectors = motion_vectors.size(); ABSL_CHECK_EQ(num_vectors, prior_weights.size()); @@ -2008,8 +2008,8 @@ const std::vector<const MotionVector*>& motion_vectors, const std::vector<float>& prior_weights, const Vector2_f& irls_scale, std::vector<float>* weights, Vector2_f* translation) const { - CHECK(weights); - CHECK(translation); + ABSL_CHECK(weights); + ABSL_CHECK(translation); const int iterations = options_.irls_iterations(); @@ -2058,8 +2058,8 @@ const std::vector<const MotionVector*>& motion_vectors, const std::vector<float>& prior_weights, const Vector2_f& irls_scale, std::vector<float>* weights, LinearSimilarityModel* lin_sim) const { - CHECK(weights); - CHECK(lin_sim); + ABSL_CHECK(weights); + ABSL_CHECK(lin_sim); const int iterations = options_.irls_iterations(); LinearSimilarityModel object_similarity; @@ -2098,7 +2098,7 @@ const std::vector<const MotionVector*>& motion_vectors, const std::vector<float>& prior_weights, const Vector2_f& irls_scale, std::vector<float>* weights, Homography* object_homography) const { - CHECK(weights); + ABSL_CHECK(weights); const int iterations = options_.irls_iterations(); Homography homography; @@ -2308,12 +2308,12 @@ std::vector<float>* inlier_weights, std::vector<float>* inlier_density, int* continued_inliers, int* swapped_inliers, float* motion_inliers_out, float* kinetic_average_out) const { - CHECK(inlier_weights); - CHECK(inlier_density); - CHECK(continued_inliers); - CHECK(swapped_inliers); - CHECK(motion_inliers_out); - CHECK(kinetic_average_out); + ABSL_CHECK(inlier_weights); + ABSL_CHECK(inlier_density); + ABSL_CHECK(continued_inliers); + ABSL_CHECK(swapped_inliers); + ABSL_CHECK(motion_inliers_out); + ABSL_CHECK(kinetic_average_out); std::unordered_map<int, int> prev_inliers; MotionBoxInliers(curr_pos, &prev_inliers); @@ -2434,9 +2434,9 @@ const std::vector<float>& weights, const std::vector<float>& density, const MotionBoxState& box_state, float* min_inlier_sum, Vector2_f* center, Vector2_f* extent) const { - CHECK(min_inlier_sum); - CHECK(center); - CHECK(extent); + ABSL_CHECK(min_inlier_sum); + ABSL_CHECK(center); + ABSL_CHECK(extent); float weight_sum = 0; float inlier_sum = 0; @@ -2653,7 +2653,7 @@ const MotionVectorFrame& motion_frame, const std::vector<const MotionBoxState*>& history, MotionBoxState* next_pos) const { - CHECK(next_pos); + ABSL_CHECK(next_pos); constexpr float kDefaultPeriodMs = 1000.0f / kTrackingDefaultFps; float temporal_scale = (motion_frame.duration_ms == 0) @@ -3163,7 +3163,7 @@ void MotionVectorFrameFromTrackingData(const TrackingData& tracking_data, MotionVectorFrame* motion_vector_frame) { - CHECK(motion_vector_frame != nullptr); + ABSL_CHECK(motion_vector_frame != nullptr); const auto& motion_data = tracking_data.motion_data(); float aspect_ratio = tracking_data.frame_aspect(); @@ -3289,7 +3289,7 @@ void InvertMotionVectorFrame(const MotionVectorFrame& input, MotionVectorFrame* output) { - CHECK(output != nullptr); + ABSL_CHECK(output != nullptr); output->background_model.CopyFrom(ModelInvert(input.background_model)); output->valid_background_model = input.valid_background_model; @@ -3342,7 +3342,7 @@ const Vector2_f& box_scaling, float max_enlarge_size, int min_num_features, std::vector<int>* inlier_indices) { - CHECK(inlier_indices); + ABSL_CHECK(inlier_indices); inlier_indices->clear(); if (features.empty()) return;
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/tracking.h b/third_party/mediapipe/src/mediapipe/util/tracking/tracking.h index 248d20b..ff8c439 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/tracking.h +++ b/third_party/mediapipe/src/mediapipe/util/tracking/tracking.h
@@ -26,12 +26,12 @@ #include <vector> #include "absl/container/flat_hash_set.h" -#include "absl/log/absl_check.h" #include "mediapipe/framework/port/vector.h" #include "mediapipe/util/tracking/flow_packager.pb.h" #include "mediapipe/util/tracking/motion_models.h" #include "mediapipe/util/tracking/motion_models.pb.h" #include "mediapipe/util/tracking/tracking.pb.h" +#include "absl/log/absl_check.h" namespace mediapipe { @@ -121,7 +121,7 @@ // existing score. inline void MotionBoxInliers(const MotionBoxState& state, std::unordered_map<int, int>* inliers) { - CHECK(inliers); + ABSL_CHECK(inliers); const int num_inliers = state.inlier_ids_size(); ABSL_DCHECK_EQ(num_inliers, state.inlier_length_size());
diff --git a/third_party/mediapipe/src/mediapipe/util/tracking/tracking_visualization_utilities.cc b/third_party/mediapipe/src/mediapipe/util/tracking/tracking_visualization_utilities.cc index 0b58608..4eaa381 100644 --- a/third_party/mediapipe/src/mediapipe/util/tracking/tracking_visualization_utilities.cc +++ b/third_party/mediapipe/src/mediapipe/util/tracking/tracking_visualization_utilities.cc
@@ -18,13 +18,14 @@ #include "mediapipe/framework/port/opencv_imgproc_inc.h" #include "mediapipe/util/tracking/box_tracker.h" #include "mediapipe/util/tracking/tracking.h" +#include "absl/log/absl_check.h" namespace mediapipe { void RenderState(const MotionBoxState& box_state, bool print_stats, cv::Mat* frame) { #ifndef NO_RENDERING - CHECK(frame != nullptr); + ABSL_CHECK(frame != nullptr); const int frame_width = frame->cols; const int frame_height = frame->rows; @@ -136,7 +137,7 @@ void RenderInternalState(const MotionBoxInternalState& internal, cv::Mat* frame) { #ifndef NO_RENDERING - CHECK(frame != nullptr); + ABSL_CHECK(frame != nullptr); const int num_vectors = internal.pos_x_size(); @@ -176,7 +177,7 @@ void RenderTrackingData(const TrackingData& data, cv::Mat* mat, bool antialiasing) { #ifndef NO_RENDERING - CHECK(mat != nullptr); + ABSL_CHECK(mat != nullptr); MotionVectorFrame mvf; MotionVectorFrameFromTrackingData(data, &mvf); @@ -205,7 +206,7 @@ void RenderBox(const TimedBoxProto& box_proto, cv::Mat* mat) { #ifndef NO_RENDERING - CHECK(mat != nullptr); + ABSL_CHECK(mat != nullptr); TimedBox box = TimedBox::FromProto(box_proto); std::array<Vector2_f, 4> corners = box.Corners(mat->cols, mat->rows);
diff --git a/third_party/mediapipe/src/platform_mappings b/third_party/mediapipe/src/platform_mappings deleted file mode 100644 index cfe26f37..0000000 --- a/third_party/mediapipe/src/platform_mappings +++ /dev/null
@@ -1,64 +0,0 @@ -# This file allows automatically mapping flags such as '--cpu' to the more -# modern Bazel platforms (https://bazel.build/concepts/platforms). - -# In particular, Bazel platforms lack support for Apple for now if no such -# mapping is put into place. It's inspired from: -# https://github.com/bazelbuild/rules_apple/issues/1764 - -platforms: - @build_bazel_apple_support//platforms:macos_x86_64 - --cpu=darwin_x86_64 - - @build_bazel_apple_support//platforms:macos_arm64 - --cpu=darwin_arm64 - - @build_bazel_apple_support//platforms:ios_i386 - --cpu=ios_i386 - - @build_bazel_apple_support//platforms:ios_x86_64 - --cpu=ios_x86_64 - - @build_bazel_apple_support//platforms:ios_sim_arm64 - --cpu=ios_sim_arm64 - - @build_bazel_apple_support//platforms:ios_armv7 - --cpu=ios_armv7 - - @build_bazel_apple_support//platforms:ios_arm64 - --cpu=ios_arm64 - - @build_bazel_apple_support//platforms:ios_arm64e - --cpu=ios_arm64e - -flags: - --cpu=darwin_x86_64 - --apple_platform_type=macos - @build_bazel_apple_support//platforms:macos_x86_64 - - --cpu=darwin_arm64 - --apple_platform_type=macos - @build_bazel_apple_support//platforms:macos_arm64 - - --cpu=ios_i386 - --apple_platform_type=ios - @build_bazel_apple_support//platforms:ios_i386 - - --cpu=ios_x86_64 - --apple_platform_type=ios - @build_bazel_apple_support//platforms:ios_x86_64 - - --cpu=ios_sim_arm64 - --apple_platform_type=ios - @build_bazel_apple_support//platforms:ios_sim_arm64 - - --cpu=ios_armv7 - --apple_platform_type=ios - @build_bazel_apple_support//platforms:ios_armv7 - - --cpu=ios_arm64 - --apple_platform_type=ios - @build_bazel_apple_support//platforms:ios_arm64 - - --cpu=ios_arm64e - --apple_platform_type=ios - @build_bazel_apple_support//platforms:ios_arm64e
diff --git a/third_party/mediapipe/src/third_party/BUILD b/third_party/mediapipe/src/third_party/BUILD index c1bee7a..971e5133 100644 --- a/third_party/mediapipe/src/third_party/BUILD +++ b/third_party/mediapipe/src/third_party/BUILD
@@ -379,9 +379,9 @@ ], ) -java_proto_library( +java_import( name = "any_java_proto", - deps = [ - "@com_google_protobuf//:any_proto", + jars = [ + "@com_google_protobuf//java/core:libcore.jar", ], )
diff --git a/third_party/mediapipe/src/third_party/com_github_glog_glog.diff b/third_party/mediapipe/src/third_party/com_github_glog_glog.diff index bf08045..15447d79 100644 --- a/third_party/mediapipe/src/third_party/com_github_glog_glog.diff +++ b/third_party/mediapipe/src/third_party/com_github_glog_glog.diff
@@ -39,3 +39,30 @@ if (append_newline) { // Fix the ostrstream back how it was before we screwed with it. // It's 99.44% certain that we don't need to worry about doing this. + +diff --git a/bazel/glog.bzl b/bazel/glog.bzl +index dacd934..d7b3d78 100644 +--- a/bazel/glog.bzl ++++ b/bazel/glog.bzl +@@ -53,7 +53,6 @@ def glog_library(namespace = "google", with_gflags = 1, **kwargs): + ) + + common_copts = [ +- "-std=c++14", + "-DGLOG_BAZEL_BUILD", + # Inject a C++ namespace. + "-DGOOGLE_NAMESPACE='%s'" % namespace, +@@ -145,7 +144,13 @@ def glog_library(namespace = "google", with_gflags = 1, **kwargs): + ], + }) + ++ c14_opts = ["-std=c++14"] ++ c17_opts = ["-std=c++17"] ++ + final_lib_copts = select({ ++ "@bazel_tools//src/conditions:windows": c17_opts, ++ "//conditions:default": c14_opts, ++ }) + select({ + "@bazel_tools//src/conditions:windows": common_copts + windows_only_copts, + "@bazel_tools//src/conditions:darwin": common_copts + linux_or_darwin_copts + darwin_only_copts, + "@bazel_tools//src/conditions:freebsd": common_copts + linux_or_darwin_copts + freebsd_only_copts,
diff --git a/third_party/mediapipe/src/third_party/external_files.bzl b/third_party/mediapipe/src/third_party/external_files.bzl index 4b51d9de..9f827c5 100644 --- a/third_party/mediapipe/src/third_party/external_files.bzl +++ b/third_party/mediapipe/src/third_party/external_files.bzl
@@ -647,6 +647,12 @@ ) http_file( + name = "com_google_mediapipe_leopard_bg_removal_result_512x512_png", + sha256 = "30be22e89fdd1d7b985294498ec67509b0caa1ca941fe291fa25f43a3873e4dd", + urls = ["https://storage.googleapis.com/mediapipe-assets/leopard_bg_removal_result_512x512.png?generation=1690239134617707"], + ) + + http_file( name = "com_google_mediapipe_leopard_bg_removal_result_png", sha256 = "afd33f2058fd58d189cda86ec931647741a6139970c9bcbc637cdd151ec657c5", urls = ["https://storage.googleapis.com/mediapipe-assets/leopard_bg_removal_result.png?generation=1685997278308542"], @@ -713,6 +719,12 @@ ) http_file( + name = "com_google_mediapipe_mobilenetsweep_dptrigmqn384_unit_384_384_fp16quant_fp32input_opt_tflite", + sha256 = "3c4c7e36b35fc903ecfb51b351b4849b23c57cc18d1416cf6cabaa1522d84760", + urls = ["https://storage.googleapis.com/mediapipe-assets/mobilenetsweep_dptrigmqn384_unit_384_384_fp16quant_fp32input_opt.tflite?generation=1690302146106240"], + ) + + http_file( name = "com_google_mediapipe_mobilenet_v1_0_25_192_quantized_1_default_1_tflite", sha256 = "f80999b6324c6f101300c3ee38fbe7e11e74a743b5e0be7350602087fe7430a3", urls = ["https://storage.googleapis.com/mediapipe-assets/mobilenet_v1_0.25_192_quantized_1_default_1.tflite?generation=1661875821863721"],
diff --git a/third_party/mediapipe/src/third_party/halide.BUILD b/third_party/mediapipe/src/third_party/halide.BUILD index 5521f6bb..677fa9f3 100644 --- a/third_party/mediapipe/src/third_party/halide.BUILD +++ b/third_party/mediapipe/src/third_party/halide.BUILD
@@ -42,7 +42,7 @@ cc_library( name = "lib_halide_static", srcs = select({ - "@mediapipe//mediapipe:windows": [ + "@halide//:halide_config_windows_x86_64": [ "bin/Release/Halide.dll", "lib/Release/Halide.lib", ],
diff --git a/third_party/mediapipe/src/third_party/halide/BUILD.bazel b/third_party/mediapipe/src/third_party/halide/BUILD.bazel index 52fbf0a1..8b69a250 100644 --- a/third_party/mediapipe/src/third_party/halide/BUILD.bazel +++ b/third_party/mediapipe/src/third_party/halide/BUILD.bazel
@@ -28,13 +28,13 @@ name = target_name, actual = select( { - "@mediapipe//mediapipe:macos_x86_64": "@macos_x86_64_halide//:%s" % target_name, - "@mediapipe//mediapipe:macos_arm64": "@macos_arm_64_halide//:%s" % target_name, - "@mediapipe//mediapipe:windows": "@windows_halide//:%s" % target_name, - # Assume Linux x86_64 by default. - # TODO: add mediapipe configs for linux to avoid assuming it's the default. - "//conditions:default": "@linux_halide//:%s" % target_name, + ":halide_config_linux_x86_64": "@linux_halide//:%s" % target_name, + ":halide_config_macos_x86_64": "@macos_x86_64_halide//:%s" % target_name, + ":halide_config_macos_arm64": "@macos_arm_64_halide//:%s" % target_name, + ":halide_config_windows_x86_64": "@windows_halide//:%s" % target_name, + # deliberately no //condition:default clause here }, + no_match_error = "Compiling Halide code requires that the build host is one of Linux x86-64, Windows x86-64, macOS x86-64, or macOS arm64.", ), ) for target_name in [
diff --git a/third_party/mediapipe/src/third_party/halide/halide.bzl b/third_party/mediapipe/src/third_party/halide/halide.bzl index 1479862..bbb0a1f 100644 --- a/third_party/mediapipe/src/third_party/halide/halide.bzl +++ b/third_party/mediapipe/src/third_party/halide/halide.bzl
@@ -82,22 +82,22 @@ # Map of halide-target-base -> config_settings _HALIDE_TARGET_CONFIG_SETTINGS_MAP = { # Android - "arm-32-android": ["@mediapipe//mediapipe:android_arm"], - "arm-64-android": ["@mediapipe//mediapipe:android_arm64"], - "x86-32-android": ["@mediapipe//mediapipe:android_x86"], - "x86-64-android": ["@mediapipe//mediapipe:android_x86_64"], + "arm-32-android": ["@halide//:halide_config_android_arm"], + "arm-64-android": ["@halide//:halide_config_android_arm64"], + "x86-32-android": ["@halide//:halide_config_android_x86_32"], + "x86-64-android": ["@halide//:halide_config_android_x86_64"], # iOS - "arm-32-ios": ["@mediapipe//mediapipe:ios_armv7"], - "arm-64-ios": ["@mediapipe//mediapipe:ios_arm64", "@mediapipe//mediapipe:ios_arm64e"], + "arm-32-ios": ["@halide//:halide_config_ios_arm"], + "arm-64-ios": ["@halide//:halide_config_ios_arm64"], # OSX (or iOS simulator) - "x86-32-osx": ["@mediapipe//mediapipe:ios_i386"], - "x86-64-osx": ["@mediapipe//mediapipe:macos_x86_64", "@mediapipe//mediapipe:ios_x86_64"], - "arm-64-osx": ["@mediapipe//mediapipe:macos_arm64"], + "x86-32-osx": ["@halide//:halide_config_macos_x86_32", "@halide//:halide_config_ios_x86_32"], + "x86-64-osx": ["@halide//:halide_config_macos_x86_64", "@halide//:halide_config_ios_x86_64"], + "arm-64-osx": ["@halide//:halide_config_macos_arm64"], # Windows - "x86-64-windows": ["@mediapipe//mediapipe:windows"], + "x86-64-windows": ["@halide//:halide_config_windows_x86_64"], # Linux - # TODO: add mediapipe configs for linux to avoid assuming it's the default. - "x86-64-linux": ["//conditions:default"], + "x86-64-linux": ["@halide//:halide_config_linux_x86_64"], + # deliberately nothing here using //conditions:default } _HALIDE_TARGET_MAP_DEFAULT = { @@ -618,6 +618,19 @@ return collections.uniq([_halide_library_runtime_target_name(f) for f in _standard_library_runtime_features()]) def halide_library_runtimes(compatible_with = []): + # Note that we don't use all of these combinations + # (and some are invalid), but that's ok. + for cpu in ["arm", "arm64", "x86_32", "x86_64"]: + for os in ["android", "linux", "windows", "ios", "macos"]: + native.config_setting( + name = "halide_config_%s_%s" % (os, cpu), + constraint_values = [ + "@platforms//os:%s" % os, + "@platforms//cpu:%s" % cpu, + ], + visibility = ["//visibility:public"], + ) + unused = [ _define_halide_library_runtime(f, compatible_with = compatible_with) for f in _standard_library_runtime_features()
diff --git a/third_party/mediapipe/src/third_party/wasm_files.bzl b/third_party/mediapipe/src/third_party/wasm_files.bzl index 9cef753..1aae204 100644 --- a/third_party/mediapipe/src/third_party/wasm_files.bzl +++ b/third_party/mediapipe/src/third_party/wasm_files.bzl
@@ -12,72 +12,72 @@ http_file( name = "com_google_mediapipe_wasm_audio_wasm_internal_js", - sha256 = "0a6d057ead24a09f116dd388146b1614f5e12559a88eb3d141e93d3f8193a29d", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_internal.js?generation=1688751355212943"], + sha256 = "9e5f88363212ac1ad505a0b9e59e3dd34413064f3b70219ff8b0216d6a53128f", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_internal.js?generation=1690577772170421"], ) http_file( name = "com_google_mediapipe_wasm_audio_wasm_internal_wasm", - sha256 = "3c475f7420f4fe5382d7123c6f5fb21fe08e2bc47e2acbc5aefd82ab589f2850", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_internal.wasm?generation=1688751357824803"], + sha256 = "8e4c7e9efcfe0d1107b40626f14070f17a817d2b830205ae642ea645fa882d28", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_internal.wasm?generation=1690577774642876"], ) http_file( name = "com_google_mediapipe_wasm_audio_wasm_nosimd_internal_js", - sha256 = "e92c7630cd873b2a3984c41287b65a338d56806baaddd2b6261bddbb4b5f2ea2", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_nosimd_internal.js?generation=1688751360158457"], + sha256 = "9b9d1fbbead06a26461bb664189d46f0c327a1077e67f0aeeb0628d04de13a81", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_nosimd_internal.js?generation=1690577777075565"], ) http_file( name = "com_google_mediapipe_wasm_audio_wasm_nosimd_internal_wasm", - sha256 = "b1445e29bc187f53f6b36da1b9ce505351b4931f16fbc8aa8b34f082dde3becf", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_nosimd_internal.wasm?generation=1688751362506882"], + sha256 = "44734a8fdb979eb9359de0c0282565d74cdced5d3a6687be849875e0eb11503c", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_nosimd_internal.wasm?generation=1690577779811164"], ) http_file( name = "com_google_mediapipe_wasm_text_wasm_internal_js", - sha256 = "095161b74dca1991d15483b9525433853c4b141e5682ca0b32f42fba7ec92ed2", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_internal.js?generation=1688751364517949"], + sha256 = "93275ebbae8dd2e9be0394391b722a0de5ac9ed51066093b1ac6ec24bebf5813", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_internal.js?generation=1690577782193422"], ) http_file( name = "com_google_mediapipe_wasm_text_wasm_internal_wasm", - sha256 = "157b3e32546e5ff6a223d2f137a4f52e89ff28c95236a5ffd9baf185559bc3f9", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_internal.wasm?generation=1688751366879784"], + sha256 = "35e734890cae0c51c1ad91e3589d5777b013bcbac64a5bcbb3a67ce4a5815dd6", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_internal.wasm?generation=1690577784996034"], ) http_file( name = "com_google_mediapipe_wasm_text_wasm_nosimd_internal_js", - sha256 = "beae70d5a1a2975cada2d8acbf291ee17a298a75018b1918405e8d6029458231", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_nosimd_internal.js?generation=1688751369120108"], + sha256 = "4e6cea3ae95ffac595bfc08f0dab4ff452c91434eb71f92c0dd34250a46825a1", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_nosimd_internal.js?generation=1690577787398460"], ) http_file( name = "com_google_mediapipe_wasm_text_wasm_nosimd_internal_wasm", - sha256 = "1223d5069ba1fa70a585a193d3d5f9bf990d043c0a1de03544ad2869daa8f03c", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_nosimd_internal.wasm?generation=1688751371734691"], + sha256 = "43cfab25c1d47822015e434d726a80d84e0bfdb5e685a511ab45d8b5cbe944d3", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_nosimd_internal.wasm?generation=1690577790301890"], ) http_file( name = "com_google_mediapipe_wasm_vision_wasm_internal_js", - sha256 = "8f97c81a2e15065828ca3877aaff90f870e15b628e902e453f28c8c59c373c8b", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_internal.js?generation=1688751373720358"], + sha256 = "6a73602a14484297690e69d716e683341b62a5fde8f5debde78de2651cb69bbe", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_internal.js?generation=1690577792657082"], ) http_file( name = "com_google_mediapipe_wasm_vision_wasm_internal_wasm", - sha256 = "a007d064939cf4f447416e1e5a777fcabe1413346e1c65982329d05b7472bbc8", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_internal.wasm?generation=1688751376340177"], + sha256 = "3431f70071f3980bf13e638551e9bb333335223e35542ee768db06501f7a26f2", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_internal.wasm?generation=1690577795814175"], ) http_file( name = "com_google_mediapipe_wasm_vision_wasm_nosimd_internal_js", - sha256 = "42e2ed5d23a36a607f81bc8f6a6801806887b4d284b520b04777230000682592", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_nosimd_internal.js?generation=1688751378413876"], + sha256 = "ece9ac1f41b93340b08682514ca291431ff7084c858caf6455e65b0c6c3eb717", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_nosimd_internal.js?generation=1690577798226032"], ) http_file( name = "com_google_mediapipe_wasm_vision_wasm_nosimd_internal_wasm", - sha256 = "2c246638f29add7cc06bc65be3c5f9eddf66296a83a90a9b697c3f6281184b9c", - urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_nosimd_internal.wasm?generation=1688751380722112"], + sha256 = "4d54739714db6b3d0fbdd0608c2824c4ccceaaf279aa4ba160f2eab2663b30f2", + urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_nosimd_internal.wasm?generation=1690577801077668"], )
diff --git a/tools/clang/rewrite_raw_ptr_fields/manual-fields-to-ignore.txt b/tools/clang/rewrite_raw_ptr_fields/manual-fields-to-ignore.txt index af014c2..107ea15 100644 --- a/tools/clang/rewrite_raw_ptr_fields/manual-fields-to-ignore.txt +++ b/tools/clang/rewrite_raw_ptr_fields/manual-fields-to-ignore.txt
@@ -246,7 +246,7 @@ # Populated manually - pointer to Objective-C object gfx::ScopedNSGraphicsContextSaveGState::context_ -base::mac::ScopedNSAutoreleasePool::autorelease_pool_ +base::apple::ScopedNSAutoreleasePool::autorelease_pool_ content::ThemeHelperMac::theme_observer_ content::PopupMenuHelper::menu_runner_ content::ShellJavaScriptDialog::helper_
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec index 544caa2..80425e00 100644 --- a/tools/gritsettings/resource_ids.spec +++ b/tools/gritsettings/resource_ids.spec
@@ -643,6 +643,10 @@ "META": {"sizes": {"includes": [20]}}, "includes": [4960], }, + "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/hats/resources.grd": { + "META": {"sizes": {"includes": [10]}}, + "includes": [4980], + }, # END chrome/ WebUI resources section # START chrome/ miscellaneous section.
diff --git a/tools/mac/power/power_sampler/battery_sampler.mm b/tools/mac/power/power_sampler/battery_sampler.mm index dee0f5a..382a4fa 100644 --- a/tools/mac/power/power_sampler/battery_sampler.mm +++ b/tools/mac/power/power_sampler/battery_sampler.mm
@@ -9,9 +9,9 @@ #include <IOKit/ps/IOPSKeys.h> #include <cstdint> +#include "base/apple/mach_logging.h" #include "base/logging.h" #include "base/mac/foundation_util.h" -#include "base/mac/mach_logging.h" #include "base/mac/scoped_cftyperef.h" #include "base/mac/scoped_ioobject.h" #include "base/memory/ptr_util.h"
diff --git a/tools/memory/simulator/process_metrics_provider_mac.cc b/tools/memory/simulator/process_metrics_provider_mac.cc index c68f1d3..40a90f1a 100644 --- a/tools/memory/simulator/process_metrics_provider_mac.cc +++ b/tools/memory/simulator/process_metrics_provider_mac.cc
@@ -8,8 +8,8 @@ #include <mach/mach_time.h> #include <mach/mach_vm.h> -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_mach_port.h" +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" #include "base/time/time.h" #include "tools/memory/simulator/utils.h"
diff --git a/tools/memory/simulator/system_metrics_provider_mac.cc b/tools/memory/simulator/system_metrics_provider_mac.cc index e557cb8..cd3683f 100644 --- a/tools/memory/simulator/system_metrics_provider_mac.cc +++ b/tools/memory/simulator/system_metrics_provider_mac.cc
@@ -7,8 +7,8 @@ #include <mach/mach.h> #include <mach/mach_time.h> -#include "base/mac/mach_logging.h" -#include "base/mac/scoped_mach_port.h" +#include "base/apple/mach_logging.h" +#include "base/apple/scoped_mach_port.h" #include "base/time/time.h" #include "tools/memory/simulator/utils.h" @@ -50,7 +50,7 @@ struct host_basic_info hostinfo; mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; - base::mac::ScopedMachSendRight host(mach_host_self()); + base::apple::ScopedMachSendRight host(mach_host_self()); int result = host_info(host.get(), HOST_BASIC_INFO, reinterpret_cast<host_info_t>(&hostinfo), &count); CHECK_EQ(result, KERN_SUCCESS);
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 27a1781..c7605ec 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -54770,6 +54770,11 @@ <int value="1" label="Detected"/> </enum> +<enum name="InternalStorageCapabilities"> + <summary>Enum for sparse histogram of capabilities</summary> + <int value="0" label="STORAGE_PRESENT"/> +</enum> + <enum name="InterruptReason"> <int value="0" label="NONE"/> <int value="1" label="FILE_FAILED"/> @@ -63575,6 +63580,7 @@ <int value="-200792853" label="UnifiedPasswordManagerAndroid:disabled"/> <int value="-200160012" label="RelatedSearchesUi:disabled"/> <int value="-199690952" label="PageInfoV2:enabled"/> + <int value="-198725896" label="AlmanacGameMigration:enabled"/> <int value="-198388861" label="ArcNearbyShareFuseBox:disabled"/> <int value="-198266781" label="FedCmWithoutWellKnownEnforcement:disabled"/> <int value="-198002129" label="MetricsSettingsAndroid:disabled"/> @@ -64192,6 +64198,7 @@ <int value="114480372" label="DeprecateAltClick:enabled"/> <int value="114541256" label="HomepagePromoCard:disabled"/> <int value="114657517" label="SecurePaymentConfirmationDebug:disabled"/> + <int value="114748684" label="AlmanacGameMigration:disabled"/> <int value="114948617" label="SurfacePolish:enabled"/> <int value="115852043" label="PageInfoAboutThisSiteKeepSidePanelOnSameTabNavs:enabled"/> @@ -93547,7 +93554,8 @@ <int value="5" label="SafeBrowsingControlledByPolicy"/> <int value="6" label="NoBrowserAvailable"/> <int value="7" label="NoBrowserWindowAvailable"/> - <int value="8" label="PreferencesNotSynced"/> + <int value="8" label="OBSOLETE_PreferencesNotSynced"/> + <int value="9" label="HistoryNotSynced"/> </enum> <enum name="SafeBrowsingTailoredSecurityOutcome"> @@ -104647,6 +104655,24 @@ <int value="5" label="Fastest"/> </enum> +<enum name="TouchSelectionDragType"> + <int value="0" label="Cursor handle drag"/> + <int value="1" label="Selection handle drag"/> + <int value="2" label="Cursor drag"/> + <int value="3" label="Long press drag"/> + <int value="4" label="Double press drag"/> +</enum> + +<enum name="TouchSelectionMenuAction"> + <int value="0" label="Cut"/> + <int value="1" label="Copy"/> + <int value="2" label="Paste"/> + <int value="3" label="Select all"/> + <int value="4" label="Select word"/> + <int value="5" label="Ellipsis"/> + <int value="6" label="Smart action"/> +</enum> + <enum name="TouchTargetAndDispatchResultType"> <int value="0" label="Non-root-scroller, non-scrollable document, not handled"/>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml index 0c904f4..c3131a6 100644 --- a/tools/metrics/histograms/metadata/ash/histograms.xml +++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -546,18 +546,6 @@ <token key="Settings" variants="AmbientUiSettings"/> </histogram> -<histogram name="Ash.AppNotificationBadgingPref" enum="Boolean" - expires_after="2022-04-10"> - <owner>mmourgos@chromium.org</owner> - <owner>gzadina@google.com</owner> - <summary> - For each user, records whether they have the app notification badging user - preference enabled or disabled. This metric is only logged when an active - user session has been started. This metric is logged periodically every 30 - minutes. - </summary> -</histogram> - <histogram name="Ash.ArcAppInitialAppsInstallDuration" units="ms" expires_after="2024-02-04"> <owner>alemate@chromium.org</owner> @@ -4883,6 +4871,30 @@ </summary> </histogram> +<histogram name="Ash.Overview.Enter.PresentationTime" units="ms" + expires_after="2024-08-14"> + <owner>sammiequon@chromium.org</owner> + <owner>zxdan@chromium.org</owner> + <owner>chromeos-wm-corexp@google.com</owner> + <summary> + Records the presentation time, which is the time in milliseconds it takes + from when the overview enter event was received and successfully processed + to when the next frame is shown to the user. + </summary> +</histogram> + +<histogram name="Ash.Overview.Exit.PresentationTime" units="ms" + expires_after="2024-08-14"> + <owner>sammiequon@chromium.org</owner> + <owner>zxdan@chromium.org</owner> + <owner>chromeos-wm-corexp@google.com</owner> + <summary> + Records the presentation time, which is the time in milliseconds it takes + from when the overview exit event was received and successfully processed to + when the next frame is shown to the user. + </summary> +</histogram> + <histogram name="Ash.Overview.Items" units="units" expires_after="2024-06-01"> <owner>sammiequon@chromium.org</owner> <owner>nupurjain@chromium.org</owner> @@ -7370,6 +7382,19 @@ </summary> </histogram> +<histogram name="Ash.WindowCycleController.Enter.PresentationTime" units="ms" + expires_after="2024-07-27"> + <owner>andp@chromium.org</owner> + <owner>sammiequon@chromium.org</owner> + <owner>chromeos-wms@google.com</owner> + <summary> + Records the presentation time, which is the time in milliseconds it takes + from when the window cycle event (alt+tab) was received to when the next + frame is shown to the user. This does not include the 150ms delay for window + cycle (for users who switch so fast they do not need the UI). + </summary> +</histogram> + <histogram name="Ash.WindowCycleController.InitialMode" enum="AltTabMode" expires_after="2024-07-27"> <owner>afakhry@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml index 56d0d85..ae502c7 100644 --- a/tools/metrics/histograms/metadata/browser/histograms.xml +++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -750,16 +750,6 @@ <token key="TabSwitchingType" variants="TabSwitchingType"/> </histogram> -<histogram name="Browser.ThemeService.WritePackToDisk" enum="BooleanSuccess" - expires_after="2022-12-25"> - <owner>etienneb@chromium.org</owner> - <owner>sky@chromium.org</owner> - <summary> - For each attempt to write a theme pack on disk, counts whether the attempt - succeeded. - </summary> -</histogram> - <histogram name="Browser.WindowCount.Guest" units="units" expires_after="2024-01-14"> <owner>rhalavati@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml index a5e02b2f..dbc6740 100644 --- a/tools/metrics/histograms/metadata/extensions/histograms.xml +++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -1242,6 +1242,26 @@ </summary> </histogram> +<histogram + name="Extensions.Events.DidDispatchToAckSucceed.{BackgroundContextType}" + units="Boolean" expires_after="never"> +<!-- expires-never: Monitors core extension system health. --> + + <owner>jlulejian@chromium.org</owner> + <owner>extensions-core@chromium.org</owner> + <summary> + Records whether an event dispatched through the event router was acked by + the renderer within a 5 minute threshold for background context of type + {BackgroundContextType}. Emits true shortly after extensions browser code + receives the ack from the renderer if within that threshold. Emits false if + that ack hasn't occurred within the threshold. The 5 minute upper bound was + chosen based on when the majority of events are ack'd. + </summary> + <token key="BackgroundContextType"> + <variant name="ExtensionServiceWorker" summary="service worker."/> + </token> +</histogram> + <histogram name="Extensions.Events.Dispatch" enum="ExtensionEvents" expires_after="never"> <!-- expires-never: Monitoring core extensions platform behavior. --> @@ -3528,6 +3548,36 @@ </summary> </histogram> +<histogram name="Extensions.ManifestVersionByLocation.{ManifestLocation}" + units="manifest version" expires_after="2024-08-14"> + <owner>rdevlin.cronin@chromium.org</owner> + <owner>extensions-core@chromium.org</owner> + <summary> + The manifest version of an enabled {ManifestLocation}. Emitted for every + enabled extension (not for other extension-y items, like platform apps) on + profile startup for user profiles (profiles where users can install + extensions, specifically profiles that can have non-component extensions + installed). + </summary> + <token key="ManifestLocation"> + <variant name="Component" + summary="component extension (ManifestLocations kComponent and + kExternalComponent)"/> + <variant name="External" + summary="external extension (ManifestLocations, kExternalPref, + kExternalPrefDownload, and kExternalRegistry)"/> + <variant name="Internal" + summary="internal extension (ManifestLocation::kInternal; likely + user-installed)"/> + <variant name="Policy" + summary="policy-installed extension (ManifestLocations + kExternalPolicy and kExternalPolicyDownload)"/> + <variant name="Unpacked" + summary="unpacked and developer extension (ManifestLocations + kCommandLine and kUnpacked)"/> + </token> +</histogram> + <histogram name="Extensions.Messaging.ExtensionPortsCreated{ExtensionMessagingPortCreationTime}" units="number of ports" expires_after="M85"> @@ -4083,6 +4133,49 @@ </histogram> <histogram + name="Extensions.ServiceWorkerBackground.ProcessManagerFinishedExternalRequestResultWithSuccessfulStart" + enum="ServiceWorkerExternalRequestResult" expires_after="2024-08-01"> + <owner>rdevlin.cronin@chromium.org</owner> + <owner>extensions-core@chromium.org</owner> + <summary> + The result from the extension process manager notifying the + ServiceWorkerContext of a service worker-based extension that an external + request that started with a result of "OK" has finished. Emitted + immediately after the call to notify the ServiceWorkerContext is made as + part of decrementing the service worker keepalive count. + </summary> +</histogram> + +<histogram + name="Extensions.ServiceWorkerBackground.ProcessManagerFinishedExternalRequestResultWithUnsuccessfulStart" + enum="ServiceWorkerExternalRequestResult" expires_after="2024-08-01"> + <owner>rdevlin.cronin@chromium.org</owner> + <owner>extensions-core@chromium.org</owner> + <summary> + The result from the extension process manager notifying the + ServiceWorkerContext of a service worker-based extension that an external + request that started with a result of anything other than "OK" has + finished. Emitted immediately after the call to notify the + ServiceWorkerContext is made as part of decrementing the service worker + keepalive count. + </summary> +</histogram> + +<histogram + name="Extensions.ServiceWorkerBackground.ProcessManagerStartingExternalRequestResult" + enum="ServiceWorkerExternalRequestResult" expires_after="2024-08-01"> + <owner>rdevlin.cronin@chromium.org</owner> + <owner>extensions-core@chromium.org</owner> + <summary> + The result from the extension process manager notifying the + ServiceWorkerContext of a service worker-based extension that an external + request has started as part of incrementing the service worker keepalive + count. Emitted immediately after the call to to notify the + ServiceWorkerContext is made. + </summary> +</histogram> + +<histogram name="Extensions.ServiceWorkerBackground.RegistrationMismatchLocation" enum="ExtensionLocation" expires_after="2023-04-01"> <owner>lazyboy@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/input/histograms.xml b/tools/metrics/histograms/metadata/input/histograms.xml index d2c76a1..0681af2 100644 --- a/tools/metrics/histograms/metadata/input/histograms.xml +++ b/tools/metrics/histograms/metadata/input/histograms.xml
@@ -1455,6 +1455,26 @@ <summary>How emojis were inserted.</summary> </histogram> +<histogram name="InputMethod.TouchSelection.DragType" + enum="TouchSelectionDragType" expires_after="2024-02-02"> + <owner>michellegc@google.com</owner> + <owner>essential-inputs-team@google.com</owner> + <summary> + Type of dragging gesture used to adjust the selection or cursor position on + ChromeOS. Recorded at the end of the drag. + </summary> +</histogram> + +<histogram name="InputMethod.TouchSelection.MenuAction" + enum="TouchSelectionMenuAction" expires_after="2024-02-02"> + <owner>michellegc@google.com</owner> + <owner>essential-inputs-team@google.com</owner> + <summary> + Action requested by a ChromeOS user from the touch selection menu. Recorded + when the menu button is pressed. + </summary> +</histogram> + <histogram name="InputMethod.VirtualKeyboard.BackspaceCount" units="units" expires_after="2023-12-24"> <owner>shend@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml index e080b04..fa35a94 100644 --- a/tools/metrics/histograms/metadata/others/histograms.xml +++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -12311,7 +12311,7 @@ </histogram> <histogram name="SSORecallPromo.PromoSeenCount" units="units" - expires_after="2023-09-24"> + expires_after="2024-04-01"> <owner>jlebel@chromium.org</owner> <owner>chrome-signin-team@google.com</owner> <summary>
diff --git a/tools/metrics/histograms/metadata/platform/histograms.xml b/tools/metrics/histograms/metadata/platform/histograms.xml index 56e7db7..1e9300f 100644 --- a/tools/metrics/histograms/metadata/platform/histograms.xml +++ b/tools/metrics/histograms/metadata/platform/histograms.xml
@@ -1793,6 +1793,16 @@ </summary> </histogram> +<histogram name="Platform.StorageCapabilities" + enum="InternalStorageCapabilities" expires_after="2024-08-15"> + <owner>dlunev@chromium.org</owner> + <owner>chromeos-storage@google.com</owner> + <summary> + Sparse histogram of internal storage device capabilities. Sampled once per + boot. + </summary> +</histogram> + <histogram name="Platform.SwapInDaily" units="pages" expires_after="2024-01-14"> <owner>asavery@chromium.org</owner> <owner>chromeos-memory@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml index efa3310..faddacd 100644 --- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml +++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -190,34 +190,27 @@ </histogram> <histogram name="SafeBrowsing.AndroidTelemetry.ApkDownload.IncompleteReason" - enum="ApkDownloadTelemetryIncompleteReason" expires_after="2023-06-10"> + enum="ApkDownloadTelemetryIncompleteReason" expires_after="2024-08-16"> <owner>xinghuilu@chromium.org</owner> <owner>chrome-counter-abuse-alerts@google.com</owner> <summary> Records if the telemetry ping sent for APK download contained a full referrer chain, or if there was an error collecting the referrer chain. Logged each time a user opted into extended reporting downloads an APK file - from a normal window on Android. - </summary> -</histogram> - -<histogram name="SafeBrowsing.AndroidTelemetry.ApkDownload.IsMimeTypeApk" - enum="BooleanIsMimeTypeApk" expires_after="2023-08-27"> - <owner>xinghuilu@chromium.org</owner> - <owner>chrome-counter-abuse-alerts@google.com</owner> - <summary> - Records whether the mime type of the download is apk. Logged each time an - apk download report is sent on Android. + from a normal window on Android. Warning: this histogram was expired from + 2023-06-10 to 2023-08-16; data may be missing. </summary> </histogram> <histogram name="SafeBrowsing.AndroidTelemetry.ApkDownload.Outcome" - enum="ApkDownloadTelemetryOutcome" expires_after="2023-07-09"> + enum="ApkDownloadTelemetryOutcome" expires_after="2024-08-16"> <owner>xinghuilu@chromium.org</owner> <owner>chrome-counter-abuse-alerts@google.com</owner> <summary> Records whether a telemetry ping for APK download was sent, or if not, then - why not. Logged each time a user downloads an APK file on Android. + why not. Logged each time a user downloads an APK file on Android. Warning: + this histogram was expired from 2023-07-09 to 2023-08-16; data may be + missing. </summary> </histogram> @@ -245,29 +238,6 @@ </histogram> <histogram - name="SafeBrowsing.BrowserThrottle.FastRequestFromNetwork.RequestDestination" - enum="RequestDestination" expires_after="2023-12-24"> - <owner>xinghuilu@chromium.org</owner> - <owner>chrome-counter-abuse-alerts@google.com</owner> - <summary> - Logs the request destination of the URL that is checked by browser URL - loader throttle. Logged each time the resource is fetched over the network - and it is faster than 2ms. - </summary> -</histogram> - -<histogram name="SafeBrowsing.BrowserThrottle.FastRequestFromNetwork.UrlScheme" - enum="SafeBrowsingUrlScheme" expires_after="2023-07-07"> - <owner>xinghuilu@chromium.org</owner> - <owner>chrome-counter-abuse-alerts@google.com</owner> - <summary> - Logs the scheme of the URL that is checked by browser URL loader throttle. - Logged each time the resource is fetched over the network and it is faster - than 2ms. - </summary> -</histogram> - -<histogram name="SafeBrowsing.BrowserThrottle.IsCheckCompletedOnProcessResponse" enum="BooleanCompleted" expires_after="2024-02-11"> <owner>xinghuilu@chromium.org</owner> @@ -282,30 +252,6 @@ </summary> </histogram> -<histogram name="SafeBrowsing.BrowserThrottle.RedirectedExtensionUrlScheme" - enum="SafeBrowsingUrlScheme" expires_after="2024-01-14"> - <owner>xinghuilu@chromium.org</owner> - <owner>chrome-counter-abuse-alerts@google.com</owner> - <summary> - Logs the scheme of the new URL that is redirected from a chrome-extension:// - URL. Logged each time browser URL loader throttle receives a redirect - request and the scheme of the original URL is chrome-extension://. This - histogram can help us determine if it is safe to skip chrome-extension:// - URLs when the request starts. - </summary> -</histogram> - -<histogram name="SafeBrowsing.BrowserThrottle.RedirectedOriginalUrlScheme" - enum="SafeBrowsingUrlScheme" expires_after="2023-12-24"> - <owner>xinghuilu@chromium.org</owner> - <owner>chrome-counter-abuse-alerts@google.com</owner> - <summary> - Logs the scheme of the original URL that is redirected. Logged each time - browser URL loader throttle receives a redirect request. This histogram can - help us determine what scheme is safe to skip when the request starts. - </summary> -</histogram> - <histogram name="SafeBrowsing.BrowserThrottle.TotalDelay" units="ms" expires_after="2024-01-21"> <owner>xinghuilu@chromium.org</owner> @@ -2118,7 +2064,7 @@ </histogram> <histogram name="SafeBrowsing.RT.Request.UserPopulation" - enum="SafeBrowsingUserPopulation" expires_after="2023-08-27"> + enum="SafeBrowsingUserPopulation" expires_after="2024-08-16"> <owner>xinghuilu@chromium.org</owner> <owner>chrome-counter-abuse-alerts@google.com</owner> <summary> @@ -2660,7 +2606,7 @@ </histogram> <histogram name="SafeBrowsing.V4ProcessPartialUpdate.AdditionsHashesCount" - units="entries" expires_after="2023-09-03"> + units="entries" expires_after="2024-08-16"> <owner>xinghuilu@chromium.org</owner> <owner>chrome-counter-abuse-alerts@google.com</owner> <summary> @@ -2948,7 +2894,7 @@ </histogram> <histogram name="SafeBrowsing.{Process}Throttle.RequestDestination" - enum="RequestDestination" expires_after="2023-08-28"> + enum="RequestDestination" expires_after="2024-08-16"> <owner>xinghuilu@chromium.org</owner> <owner>chrome-counter-abuse-alerts@google.com</owner> <summary> @@ -2959,7 +2905,7 @@ </histogram> <histogram name="SafeBrowsing.{Process}Throttle.TotalDelay2{ResponseType}" - units="ms" expires_after="2023-08-09"> + units="ms" expires_after="2024-08-16"> <owner>xinghuilu@chromium.org</owner> <owner>chrome-counter-abuse-alerts@google.com</owner> <summary>
diff --git a/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml b/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml index 5fd98ea..3209a80 100644 --- a/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml +++ b/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
@@ -232,6 +232,81 @@ <token key="SegmentationKey" variants="SegmentationKey"/> </histogram> +<histogram + name="SegmentationPlatform.DefaultModelDelivery.Metadata.FeatureCount.{SegmentationModel}" + units="features" expires_after="2024-08-14"> + <owner>ritikagup@google.com</owner> + <owner>chrome-segmentation-platform@google.com</owner> + <summary> + Records how many tensor features an updated {SegmentationModel} segmentation + model requires in its metadata. Recorded only for embedded model. + + Recorded every time the {SegmentationModel} model is updated, which can + happen at any time, but usually happens at least once during startup. + </summary> + <token key="SegmentationModel" variants="SegmentationModel"/> +</histogram> + +<histogram + name="SegmentationPlatform.DefaultModelDelivery.Metadata.Validation.{ValidationPhase}.{SegmentationModel}" + enum="SegmentationPlatformValidationResult" expires_after="2024-08-14"> + <owner>ritikagup@google.com</owner> + <owner>chrome-segmentation-platform@google.com</owner> + <summary> + Records the result of metadata validation {ValidationPhase} the metadata has + been merged with the existing metadata for {SegmentationModel}. Recorded + only for embedded model. + + Recorded every time the {SegmentationModel} model is updated, which can + happen at any time, but usually happens at least once during startup. + </summary> + <token key="SegmentationModel" variants="SegmentationModel"/> + <token key="ValidationPhase" variants="ValidationPhase"/> +</histogram> + +<histogram name="SegmentationPlatform.DefaultModelDelivery.Received" + enum="SegmentationPlatformSegmentationModel" expires_after="2024-08-14"> + <owner>ritikagup@google.com</owner> + <owner>chrome-segmentation-platform@google.com</owner> + <summary> + Records what type of model has been received. + + Recorded every time a model is updated, which can happen at any time, but + usually happens at least once during startup. Recorded only for embedded + model. + </summary> +</histogram> + +<histogram + name="SegmentationPlatform.DefaultModelDelivery.SaveResult.{SegmentationModel}" + enum="BooleanSuccess" expires_after="2024-08-14"> + <owner>ritikagup@google.com</owner> + <owner>chrome-segmentation-platform@google.com</owner> + <summary> + Records whether an updated {SegmentationModel} segmentation model was + successfully saved. Recorded only for embedded model. + + Recorded every time the {SegmentationModel} model is updated, which can + happen at any time, but usually happens at least once during startup. + </summary> + <token key="SegmentationModel" variants="SegmentationModel"/> +</histogram> + +<histogram + name="SegmentationPlatform.DefaultModelDelivery.SegmentIdMatches.{SegmentationModel}" + enum="BooleanYesNo" expires_after="2024-08-14"> + <owner>ritikagup@google.com</owner> + <owner>chrome-segmentation-platform@google.com</owner> + <summary> + Records whether an updated {SegmentationModel} segmentation model type + matches what was stored for that type. Recorded only for embedded model. + + Recorded every time the {SegmentationModel} model is updated, which can + happen at any time, but usually happens at least once during startup. + </summary> + <token key="SegmentationModel" variants="SegmentationModel"/> +</histogram> + <histogram name="SegmentationPlatform.DeviceCountByOsType.{OsType}" units="devices" expires_after="2023-10-01"> <owner>junzou@chromium.org</owner> @@ -410,7 +485,7 @@ <owner>chrome-segmentation-platform@google.com</owner> <summary> Records how many tensor features an updated {SegmentationModel} segmentation - model requires in its metadata. + model requires in its metadata. Recorded only for server model. Recorded every time the {SegmentationModel} model is updated, which can happen at any time, but usually happens at least once during startup. @@ -426,7 +501,8 @@ <owner>chrome-segmentation-platform@google.com</owner> <summary> Records the result of metadata validation {ValidationPhase} the metadata has - been merged with the existing metadata for {SegmentationModel}. + been merged with the existing metadata for {SegmentationModel}. Recorded + only for server model. Recorded every time the {SegmentationModel} model is updated, which can happen at any time, but usually happens at least once during startup. @@ -444,7 +520,8 @@ Records what type of model has been received. Recorded every time a model is updated, which can happen at any time, but - usually happens at least once during startup. + usually happens at least once during startup. Recorded only for server + model. </summary> </histogram> @@ -456,7 +533,7 @@ <owner>chrome-segmentation-platform@google.com</owner> <summary> Records whether an updated {SegmentationModel} segmentation model was - successfully saved. + successfully saved. Recorded only for server model. Recorded every time the {SegmentationModel} model is updated, which can happen at any time, but usually happens at least once during startup. @@ -472,7 +549,7 @@ <owner>chrome-segmentation-platform@google.com</owner> <summary> Records whether an updated {SegmentationModel} segmentation model type - matches what was stored for that type. + matches what was stored for that type. Recorded only for server model. Recorded every time the {SegmentationModel} model is updated, which can happen at any time, but usually happens at least once during startup.
diff --git a/ui/base/ime/ash/BUILD.gn b/ui/base/ime/ash/BUILD.gn index 4f8e3ad..71be0515 100644 --- a/ui/base/ime/ash/BUILD.gn +++ b/ui/base/ime/ash/BUILD.gn
@@ -63,6 +63,7 @@ defines = [ "IS_UI_BASE_IME_ASH_IMPL" ] public_deps = [ + "//third_party/abseil-cpp:absl", "//ui/base/ime", "//url", ]
diff --git a/ui/base/ime/ash/component_extension_ime_manager.h b/ui/base/ime/ash/component_extension_ime_manager.h index c073c8773..09da9a39 100644 --- a/ui/base/ime/ash/component_extension_ime_manager.h +++ b/ui/base/ime/ash/component_extension_ime_manager.h
@@ -11,6 +11,7 @@ #include "base/component_export.h" #include "base/files/file_path.h" +#include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/base/ime/ash/component_extension_ime_manager_delegate.h" #include "ui/base/ime/ash/input_method_descriptor.h" @@ -28,6 +29,7 @@ std::string indicator; std::vector<std::string> language_codes; // e.g. "en". std::string layout; + absl::optional<std::string> handwriting_language; GURL options_page_url; GURL input_view_url; };
diff --git a/ui/base/test/cocoa_helper.h b/ui/base/test/cocoa_helper.h index 684826a..27b86bd 100644 --- a/ui/base/test/cocoa_helper.h +++ b/ui/base/test/cocoa_helper.h
@@ -10,7 +10,7 @@ #include <memory> -#import "base/mac/scoped_nsautorelease_pool.h" +#import "base/apple/scoped_nsautorelease_pool.h" #include "testing/platform_test.h" #include "ui/display/screen.h" @@ -86,7 +86,7 @@ display::ScopedNativeScreen screen_; - base::mac::ScopedNSAutoreleasePool pool_; + base::apple::ScopedNSAutoreleasePool pool_; // Windows which existed at the beginning of the test. WeakWindowVector initial_windows_;
diff --git a/ui/compositor/test/test_compositor_host_mac.mm b/ui/compositor/test/test_compositor_host_mac.mm index 9afd11fa..7f5b0ace 100644 --- a/ui/compositor/test/test_compositor_host_mac.mm +++ b/ui/compositor/test/test_compositor_host_mac.mm
@@ -9,8 +9,8 @@ #include <memory> +#include "base/apple/scoped_nsautorelease_pool.h" #include "base/compiler_specific.h" -#include "base/mac/scoped_nsautorelease_pool.h" #include "base/memory/raw_ptr.h" #include "base/task/single_thread_task_runner.h" #include "components/viz/common/surfaces/local_surface_id.h" @@ -60,11 +60,11 @@ protected: FoundationHost() - : pool_(std::make_unique<base::mac::ScopedNSAutoreleasePool>()) {} + : pool_(std::make_unique<base::apple::ScopedNSAutoreleasePool>()) {} virtual ~FoundationHost() = default; private: - std::unique_ptr<base::mac::ScopedNSAutoreleasePool> pool_; + std::unique_ptr<base::apple::ScopedNSAutoreleasePool> pool_; }; // Tests that use the AppKit framework need to have the NSApplication
diff --git a/ui/gfx/mac/io_surface.cc b/ui/gfx/mac/io_surface.cc index 487e029..ba37732 100644 --- a/ui/gfx/mac/io_surface.cc +++ b/ui/gfx/mac/io_surface.cc
@@ -9,12 +9,12 @@ #include <stddef.h> #include <stdint.h> +#include "base/apple/mach_logging.h" #include "base/bits.h" #include "base/command_line.h" #include "base/feature_list.h" #include "base/logging.h" #include "base/mac/mac_util.h" -#include "base/mac/mach_logging.h" #include "base/metrics/histogram_macros.h" #include "base/trace_event/trace_event.h" #include "ui/gfx/buffer_format_util.h"
diff --git a/ui/gfx/mojom/buffer_types_mojom_traits.cc b/ui/gfx/mojom/buffer_types_mojom_traits.cc index 1276c760..dcd2b2d 100644 --- a/ui/gfx/mojom/buffer_types_mojom_traits.cc +++ b/ui/gfx/mojom/buffer_types_mojom_traits.cc
@@ -45,7 +45,7 @@ IOSurfaceCreateMachPort(handle.io_surface.get())); return gfx::mojom::GpuMemoryBufferPlatformHandle::NewMachPort( mojo::PlatformHandle( - base::mac::RetainMachSendRight(io_surface_mach_port.get()))); + base::apple::RetainMachSendRight(io_surface_mach_port.get()))); #else break; #endif
diff --git a/ui/gfx/mojom/ca_layer_params_mojom_traits.cc b/ui/gfx/mojom/ca_layer_params_mojom_traits.cc index a4296b8..e1c6ed1 100644 --- a/ui/gfx/mojom/ca_layer_params_mojom_traits.cc +++ b/ui/gfx/mojom/ca_layer_params_mojom_traits.cc
@@ -17,7 +17,7 @@ if (ca_layer_params.io_surface_mach_port) { DCHECK(!ca_layer_params.ca_context_id); return gfx::mojom::CALayerContent::NewIoSurfaceMachPort( - mojo::PlatformHandle(base::mac::RetainMachSendRight( + mojo::PlatformHandle(base::apple::RetainMachSendRight( ca_layer_params.io_surface_mach_port.get()))); } #endif
diff --git a/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc b/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc index efcd571..cd298cc 100644 --- a/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc +++ b/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc
@@ -11,6 +11,7 @@ #include "base/check.h" #include "base/logging.h" #include "base/strings/utf_string_conversions.h" +#include "base/version.h" #include "ui/ozone/common/features.h" #include "ui/ozone/platform/wayland/host/wayland_connection.h" #include "ui/ozone/platform/wayland/host/wayland_output_manager.h" @@ -22,7 +23,7 @@ namespace { constexpr uint32_t kMinVersion = 1; -constexpr uint32_t kMaxVersion = 57; +constexpr uint32_t kMaxVersion = 58; } // static @@ -66,12 +67,17 @@ DCHECK(obj_); DCHECK(connection_); - static constexpr zaura_shell_listener zaura_shell_listener = { - &OnLayoutMode, &OnBugFix, - &OnDesksChanged, &OnDeskActivationChanged, - &OnActivated, &SetOverviewMode, - &UnsetOverviewMode}; - zaura_shell_add_listener(obj_.get(), &zaura_shell_listener, this); + static constexpr zaura_shell_listener kZAuraShellListener = { + .layout_mode = &OnLayoutMode, + .bug_fix = &OnBugFix, + .desks_changed = &OnDesksChanged, + .desk_activation_changed = &OnDeskActivationChanged, + .activated = &OnActivated, + .set_overview_mode = &OnSetOverviewMode, + .unset_overview_mode = &OnUnsetOverviewMode, + .compositor_version = &OnCompositorVersion}; + zaura_shell_add_listener(obj_.get(), &kZAuraShellListener, this); + if (IsWaylandSurfaceSubmissionInPixelCoordinatesEnabled() && zaura_shell_get_version(wl_object()) >= ZAURA_TOPLEVEL_SURFACE_SUBMISSION_IN_PIXEL_COORDINATES_SINCE_VERSION) { @@ -165,8 +171,8 @@ wl_surface* lost_active) {} // static -void WaylandZAuraShell::SetOverviewMode(void* data, - struct zaura_shell* zaura_shell) { +void WaylandZAuraShell::OnSetOverviewMode(void* data, + struct zaura_shell* zaura_shell) { #if BUILDFLAG(IS_CHROMEOS_LACROS) auto* self = static_cast<WaylandZAuraShell*>(data); for (auto* window : self->connection_->window_manager()->GetAllWindows()) { @@ -178,8 +184,8 @@ } // static -void WaylandZAuraShell::UnsetOverviewMode(void* data, - struct zaura_shell* zaura_shell) { +void WaylandZAuraShell::OnUnsetOverviewMode(void* data, + struct zaura_shell* zaura_shell) { #if BUILDFLAG(IS_CHROMEOS_LACROS) auto* self = static_cast<WaylandZAuraShell*>(data); for (auto* window : self->connection_->window_manager()->GetAllWindows()) { @@ -190,4 +196,21 @@ #endif } +// static +void WaylandZAuraShell::OnCompositorVersion(void* data, + struct zaura_shell* zaura_shell, + const char* version_label) { + auto* self = static_cast<WaylandZAuraShell*>(data); + base::Version compositor_version(version_label); + if (!compositor_version.IsValid()) { + LOG(WARNING) << "Invalid compositor version string received."; + self->compositor_version_ = {}; + return; + } + + DCHECK_EQ(compositor_version.components().size(), 4u); + DVLOG(1) << "Wayland compositor version: " << compositor_version; + self->compositor_version_ = compositor_version; +} + } // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_zaura_shell.h b/ui/ozone/platform/wayland/host/wayland_zaura_shell.h index 2c2ecc7..7d91c69 100644 --- a/ui/ozone/platform/wayland/host/wayland_zaura_shell.h +++ b/ui/ozone/platform/wayland/host/wayland_zaura_shell.h
@@ -7,6 +7,7 @@ #include "base/containers/flat_set.h" #include "base/memory/raw_ptr.h" +#include "base/version.h" #include "build/chromeos_buildflags.h" #include "ui/display/tablet_state.h" #include "ui/ozone/platform/wayland/common/wayland_object.h" @@ -39,18 +40,28 @@ ~WaylandZAuraShell(); zaura_shell* wl_object() const { return obj_.get(); } + + // Returns the Wayland compositor version. If the bound zaura_shell is not + // recent enough (ie: < v58), the returned version is not valid. This can be + // used in conjunction with ozone platform's RuntimeProperties + (optional) + // HasBugFix() calls in order to determine if Exo supports a given feature. + // See https://crbug.com/1457008. + const base::Version& compositor_version() const { + return compositor_version_; + } // Due to version skew between Lacros and Ash, there may be certain bug // fixes in one but not in the other (crbug.com/1151508). Lacros can use // |HasBugFix| to provide a temporary workaround to an exo bug until Ash // uprevs and starts reporting that a given bug ID has been fixed. bool HasBugFix(uint32_t id); + std::string GetDeskName(int index) const; int GetNumberOfDesks(); int GetActiveDeskIndex() const; display::TabletState GetTabletState() const; private: - // zaura_shell_listeners + // zaura_shell_listener handler functions: static void OnLayoutMode(void* data, struct zaura_shell* zaura_shell, uint32_t layout_mode); @@ -67,11 +78,15 @@ struct zaura_shell* zaura_shell, struct wl_surface* gained_active, struct wl_surface* lost_active); - static void SetOverviewMode(void* data, struct zaura_shell* zaura_shell); - static void UnsetOverviewMode(void* data, struct zaura_shell* zaura_shell); + static void OnSetOverviewMode(void* data, struct zaura_shell* zaura_shell); + static void OnUnsetOverviewMode(void* data, struct zaura_shell* zaura_shell); + static void OnCompositorVersion(void* data, + struct zaura_shell* zaura_shell, + const char* version_label); wl::Object<zaura_shell> obj_; const raw_ptr<WaylandConnection> connection_; + base::Version compositor_version_; base::flat_set<uint32_t> bug_fix_ids_; std::vector<std::string> desks_; int active_desk_index_ = 0;
diff --git a/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc b/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc index 39df6275..f459756 100644 --- a/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc +++ b/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc
@@ -4,22 +4,23 @@ #include "ui/ozone/platform/wayland/host/wayland_zaura_shell.h" +#include "base/version.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h" #include "ui/ozone/platform/wayland/test/test_zaura_shell.h" #include "ui/ozone/platform/wayland/test/wayland_test.h" -using testing::Values; - namespace ui { using WaylandZAuraShellTest = WaylandTestSimpleWithAuraShell; TEST_F(WaylandZAuraShellTest, BugFix) { + ASSERT_FALSE(connection_->zaura_shell()->HasBugFix(1)); + ASSERT_FALSE(connection_->zaura_shell()->HasBugFix(3)); + ASSERT_FALSE(connection_->zaura_shell()->HasBugFix(2)); + PostToServerAndWait([](wl::TestWaylandServerThread* server) { - auto* const zaura_shell = server->zaura_shell()->resource(); - zaura_shell_send_bug_fix(zaura_shell, 1); - zaura_shell_send_bug_fix(zaura_shell, 3); + server->zaura_shell()->SetBugFixes({1, 3}); }); ASSERT_TRUE(connection_->zaura_shell()->HasBugFix(1)); @@ -27,4 +28,25 @@ ASSERT_FALSE(connection_->zaura_shell()->HasBugFix(2)); } +TEST_F(WaylandZAuraShellTest, CompositorVersion) { + PostToServerAndWait([](wl::TestWaylandServerThread* server) { + server->zaura_shell()->SetCompositorVersion("INVALID.VERSION"); + }); + ASSERT_FALSE(connection_->zaura_shell()->compositor_version().IsValid()); + + PostToServerAndWait([](wl::TestWaylandServerThread* server) { + server->zaura_shell()->SetCompositorVersion("1.2.3.4"); + }); + + const base::Version& received_version = + connection_->zaura_shell()->compositor_version(); + ASSERT_TRUE(received_version.IsValid()); + ASSERT_EQ(received_version, base::Version("1.2.3.4")); + + PostToServerAndWait([](wl::TestWaylandServerThread* server) { + server->zaura_shell()->SetCompositorVersion("1NV4L1D.2"); + }); + ASSERT_FALSE(connection_->zaura_shell()->compositor_version().IsValid()); +} + } // namespace ui
diff --git a/ui/ozone/platform/wayland/test/test_zaura_shell.cc b/ui/ozone/platform/wayland/test/test_zaura_shell.cc index 6eeb286..7950ca3 100644 --- a/ui/ozone/platform/wayland/test/test_zaura_shell.cc +++ b/ui/ozone/platform/wayland/test/test_zaura_shell.cc
@@ -17,7 +17,7 @@ namespace { -constexpr uint32_t kZAuraShellVersion = 56; +constexpr uint32_t kZAuraShellVersion = 58; constexpr uint32_t kZAuraOutputVersion = 44; void GetAuraSurface(wl_client* client, @@ -87,15 +87,34 @@ TestZAuraShell::~TestZAuraShell() = default; +void TestZAuraShell::SetCompositorVersion(const std::string& version_string) { + if (version_string == compositor_version_string_) { + return; + } + compositor_version_string_ = version_string; + MaybeSendCompositorVersion(); +} + void TestZAuraShell::SetBugFixes(std::vector<uint32_t> bug_fixes) { bug_fixes_ = std::move(bug_fixes); MaybeSendBugFixes(); } void TestZAuraShell::OnBind() { + MaybeSendCompositorVersion(); MaybeSendBugFixes(); } +void TestZAuraShell::MaybeSendCompositorVersion() { + if (resource() && + wl_resource_get_version(resource()) >= + ZAURA_SHELL_COMPOSITOR_VERSION_SINCE_VERSION && + !compositor_version_string_.empty()) { + zaura_shell_send_compositor_version(resource(), + compositor_version_string_.c_str()); + } +} + void TestZAuraShell::MaybeSendBugFixes() { if (resource() && wl_resource_get_version(resource()) >= ZAURA_SHELL_BUG_FIX_SINCE_VERSION) {
diff --git a/ui/ozone/platform/wayland/test/test_zaura_shell.h b/ui/ozone/platform/wayland/test/test_zaura_shell.h index 9afb909..7c68e03 100644 --- a/ui/ozone/platform/wayland/test/test_zaura_shell.h +++ b/ui/ozone/platform/wayland/test/test_zaura_shell.h
@@ -7,6 +7,8 @@ #include <aura-shell-server-protocol.h> +#include <string> + #include "testing/gmock/include/gmock/gmock.h" #include "ui/ozone/platform/wayland/test/global_object.h" @@ -21,14 +23,21 @@ ~TestZAuraShell() override; + void SetCompositorVersion(const std::string& version); + // Sets bug fixes and sends them out if the object is bound. void SetBugFixes(std::vector<uint32_t> bug_fixes); private: void OnBind() override; + void MaybeSendCompositorVersion(); void MaybeSendBugFixes(); + // Compostitor string version. For testing purposes, it is help in a string, + // so that it can store either valid or invalid values. + std::string compositor_version_string_; + // Bug fixes that shall be sent to the client. std::vector<uint32_t> bug_fixes_; };
diff --git a/ui/touch_selection/BUILD.gn b/ui/touch_selection/BUILD.gn index 1f96222..074bcc0 100644 --- a/ui/touch_selection/BUILD.gn +++ b/ui/touch_selection/BUILD.gn
@@ -24,6 +24,8 @@ "touch_selection_draggable.h", "touch_selection_menu_runner.cc", "touch_selection_menu_runner.h", + "touch_selection_metrics.cc", + "touch_selection_metrics.h", "ui_touch_selection_export.h", ]
diff --git a/ui/touch_selection/touch_selection_controller.cc b/ui/touch_selection/touch_selection_controller.cc index da8c0404..a2fcf10b 100644 --- a/ui/touch_selection/touch_selection_controller.cc +++ b/ui/touch_selection/touch_selection_controller.cc
@@ -11,6 +11,7 @@ #include "base/metrics/histogram_macros.h" #include "base/metrics/user_metrics.h" #include "base/notreached.h" +#include "ui/touch_selection/touch_selection_metrics.h" namespace ui { namespace { @@ -198,6 +199,7 @@ const gfx::PointF& location) { longpress_drag_selector_.OnLongPressEvent(event_time, location); response_pending_input_event_ = LONG_PRESS; + drag_selector_initiating_gesture_ = DragSelectorInitiatingGesture::kLongPress; } void TouchSelectionController::HandleDoublePressEvent( @@ -205,6 +207,8 @@ const gfx::PointF& location) { longpress_drag_selector_.OnDoublePressEvent(event_time, location); response_pending_input_event_ = LONG_PRESS; + drag_selector_initiating_gesture_ = + DragSelectorInitiatingGesture::kDoublePress; } void TouchSelectionController::OnScrollBeginEvent() { @@ -380,7 +384,6 @@ void TouchSelectionController::OnSwipeToMoveCursorBegin() { if (config_.hide_active_handle) { - // Hide the handle when magnifier is showing since it can confuse the user. SetTemporarilyHidden(true); // If the user has typed something, the insertion handle might be hidden. @@ -390,9 +393,10 @@ } void TouchSelectionController::OnSwipeToMoveCursorEnd() { - // Show the handle at the end if magnifier was showing. - if (config_.hide_active_handle) + if (config_.hide_active_handle) { SetTemporarilyHidden(false); + } + RecordTouchSelectionDrag(ui::TouchSelectionDragType::kCursorDrag); } void TouchSelectionController::OnDragBegin( @@ -477,10 +481,13 @@ void TouchSelectionController::OnDragEnd( const TouchSelectionDraggable& draggable) { - if (&draggable == insertion_handle_.get()) + if (&draggable == insertion_handle_.get()) { client_->OnSelectionEvent(INSERTION_HANDLE_DRAG_STOPPED); - else + } else { client_->OnSelectionEvent(SELECTION_HANDLE_DRAG_STOPPED); + } + LogDragType(draggable); + drag_selector_initiating_gesture_ = DragSelectorInitiatingGesture::kNone; } bool TouchSelectionController::IsWithinTapSlop( @@ -710,4 +717,20 @@ } } +void TouchSelectionController::LogDragType( + const TouchSelectionDraggable& draggable) { + if (&draggable == insertion_handle_.get()) { + RecordTouchSelectionDrag(ui::TouchSelectionDragType::kCursorHandleDrag); + } else if (&draggable == start_selection_handle_.get() || + &draggable == end_selection_handle_.get()) { + RecordTouchSelectionDrag(ui::TouchSelectionDragType::kSelectionHandleDrag); + } else if (drag_selector_initiating_gesture_ == + DragSelectorInitiatingGesture::kLongPress) { + RecordTouchSelectionDrag(ui::TouchSelectionDragType::kLongPressDrag); + } else if (drag_selector_initiating_gesture_ == + DragSelectorInitiatingGesture::kDoublePress) { + RecordTouchSelectionDrag(ui::TouchSelectionDragType::kDoublePressDrag); + } +} + } // namespace ui
diff --git a/ui/touch_selection/touch_selection_controller.h b/ui/touch_selection/touch_selection_controller.h index c6e57a6..4fb88a6c6 100644 --- a/ui/touch_selection/touch_selection_controller.h +++ b/ui/touch_selection/touch_selection_controller.h
@@ -166,6 +166,8 @@ enum InputEventType { TAP, REPEATED_TAP, LONG_PRESS, INPUT_EVENT_TYPE_NONE }; + enum class DragSelectorInitiatingGesture { kNone, kLongPress, kDoublePress }; + bool WillHandleTouchEventImpl(const MotionEvent& event); // TouchHandleClient implementation. @@ -215,6 +217,7 @@ TouchHandle::AnimationStyle GetAnimationStyle(bool was_active) const; void LogSelectionEnd(); + void LogDragType(const TouchSelectionDraggable& draggable); const raw_ptr<TouchSelectionControllerClient, DanglingUntriaged> client_; const Config config_; @@ -244,9 +247,15 @@ // between lines. bool anchor_drag_to_selection_start_; - // Longpress drag allows direct manipulation of longpress-initiated selection. + // Allows the text selection to be adjusted by touch dragging after a long + // press or double press initiated selection. LongPressDragSelector longpress_drag_selector_; + // Used to track whether a selection drag gesture was initiated by a long + // press or double press. + DragSelectorInitiatingGesture drag_selector_initiating_gesture_ = + DragSelectorInitiatingGesture::kNone; + gfx::RectF viewport_rect_; base::TimeTicks selection_start_time_;
diff --git a/ui/touch_selection/touch_selection_metrics.cc b/ui/touch_selection/touch_selection_metrics.cc new file mode 100644 index 0000000..cf8729d1 --- /dev/null +++ b/ui/touch_selection/touch_selection_metrics.cc
@@ -0,0 +1,53 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/touch_selection/touch_selection_metrics.h" + +#include "base/metrics/histogram_functions.h" +#include "ui/base/pointer/touch_editing_controller.h" + +namespace ui { + +namespace { + +TouchSelectionMenuAction MapCommandIdToMenuAction(int command_id) { + switch (command_id) { + case ui::TouchEditable::kCut: + return TouchSelectionMenuAction::kCut; + case ui::TouchEditable::kCopy: + return TouchSelectionMenuAction::kCopy; + case ui::TouchEditable::kPaste: + return TouchSelectionMenuAction::kPaste; + case ui::TouchEditable::kSelectAll: + return TouchSelectionMenuAction::kSelectAll; + case ui::TouchEditable::kSelectWord: + return TouchSelectionMenuAction::kSelectWord; + default: + NOTREACHED_NORETURN() << "Invalid command id: " << command_id; + } +} + +} // namespace + +void RecordTouchSelectionDrag(TouchSelectionDragType drag_type) { + base::UmaHistogramEnumeration(kTouchSelectionDragTypeHistogramName, + drag_type); +} + +void RecordTouchSelectionMenuCommandAction(int command_id) { + base::UmaHistogramEnumeration(kTouchSelectionMenuActionHistogramName, + MapCommandIdToMenuAction(command_id)); +} + +void RecordTouchSelectionMenuEllipsisAction() { + base::UmaHistogramEnumeration(kTouchSelectionMenuActionHistogramName, + TouchSelectionMenuAction::kEllipsis); +} + +void RecordTouchSelectionMenuSmartAction() { + base::UmaHistogramEnumeration(kTouchSelectionMenuActionHistogramName, + TouchSelectionMenuAction::kSmartAction); +} + +} // namespace ui
diff --git a/ui/touch_selection/touch_selection_metrics.h b/ui/touch_selection/touch_selection_metrics.h new file mode 100644 index 0000000..fb99da2e --- /dev/null +++ b/ui/touch_selection/touch_selection_metrics.h
@@ -0,0 +1,52 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_TOUCH_SELECTION_TOUCH_SELECTION_METRICS_H_ +#define UI_TOUCH_SELECTION_TOUCH_SELECTION_METRICS_H_ + +#include "ui/touch_selection/ui_touch_selection_export.h" + +namespace ui { + +inline constexpr char kTouchSelectionDragTypeHistogramName[] = + "InputMethod.TouchSelection.DragType"; + +inline constexpr char kTouchSelectionMenuActionHistogramName[] = + "InputMethod.TouchSelection.MenuAction"; + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class TouchSelectionDragType { + kCursorHandleDrag = 0, + kSelectionHandleDrag = 1, + kCursorDrag = 2, + kLongPressDrag = 3, + kDoublePressDrag = 4, + kMaxValue = kDoublePressDrag +}; + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class TouchSelectionMenuAction { + kCut = 0, + kCopy = 1, + kPaste = 2, + kSelectAll = 3, + kSelectWord = 4, + kEllipsis = 5, + kSmartAction = 6, + kMaxValue = kSmartAction +}; + +UI_TOUCH_SELECTION_EXPORT void RecordTouchSelectionDrag( + TouchSelectionDragType drag_type); + +UI_TOUCH_SELECTION_EXPORT void RecordTouchSelectionMenuCommandAction( + int command_id); +UI_TOUCH_SELECTION_EXPORT void RecordTouchSelectionMenuEllipsisAction(); +UI_TOUCH_SELECTION_EXPORT void RecordTouchSelectionMenuSmartAction(); + +} // namespace ui + +#endif // UI_TOUCH_SELECTION_TOUCH_SELECTION_METRICS_H_
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn index 1ca1591..2422a0c 100644 --- a/ui/views/BUILD.gn +++ b/ui/views/BUILD.gn
@@ -548,6 +548,7 @@ "//ui/native_theme:native_theme_browser", "//ui/resources", "//ui/strings", + "//ui/touch_selection", "//ui/views/debug:views_debug", "//ui/views/resources", "//url", @@ -805,7 +806,6 @@ "//ui/events", "//ui/platform_window", "//ui/platform_window/wm", - "//ui/touch_selection", "//ui/wm", "//ui/wm/public", ] @@ -1346,6 +1346,7 @@ "//ui/resources", "//ui/resources:ui_test_pak", "//ui/strings", + "//ui/touch_selection", "//url", ] @@ -1460,7 +1461,6 @@ "//ui/aura", "//ui/aura:test_support", "//ui/base/cursor/mojom:cursor_type", - "//ui/touch_selection", "//ui/wm", "//ui/wm/public", ]
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc index f76c3a0..d26e6b5 100644 --- a/ui/views/controls/textfield/textfield.cc +++ b/ui/views/controls/textfield/textfield.cc
@@ -53,6 +53,7 @@ #include "ui/gfx/geometry/insets.h" #include "ui/gfx/selection_bound.h" #include "ui/strings/grit/ui_strings.h" +#include "ui/touch_selection/touch_selection_metrics.h" #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/animation/ink_drop.h" #include "ui/views/animation/ink_drop_highlight.h" @@ -2972,6 +2973,7 @@ SelectWordAt(event->location()); OnAfterUserAction(); selection_dragging_state_ = SelectionDraggingState::kSelectedWord; + selection_drag_type_ = ui::TouchSelectionDragType::kDoublePressDrag; } else if (event->details().tap_down_count() == 3) { OnBeforeUserAction(); SelectAll(false); @@ -2983,6 +2985,7 @@ return true; case ui::ET_GESTURE_LONG_PRESS: selection_dragging_state_ = SelectionDraggingState::kSelectedWord; + selection_drag_type_ = ui::TouchSelectionDragType::kLongPressDrag; DestroyTouchSelection(); event->SetHandled(); return true; @@ -3082,13 +3085,18 @@ DestroyTouchSelection(); MoveCursorTo(event.location(), false); selection_dragging_state_ = SelectionDraggingState::kDraggingCursor; + selection_drag_type_ = ui::TouchSelectionDragType::kCursorDrag; return true; } return false; } void Textfield::StopSelectionDragging() { + if (IsSelectionDragging() && selection_drag_type_.has_value()) { + ui::RecordTouchSelectionDrag(selection_drag_type_.value()); + } selection_dragging_state_ = SelectionDraggingState::kNone; + selection_drag_type_ = absl::nullopt; } BEGIN_METADATA(Textfield, View)
diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h index 9d4f824..33cd6e76 100644 --- a/ui/views/controls/textfield/textfield.h +++ b/ui/views/controls/textfield/textfield.h
@@ -34,6 +34,7 @@ #include "ui/gfx/range/range.h" #include "ui/gfx/selection_model.h" #include "ui/gfx/text_constants.h" +#include "ui/touch_selection/touch_selection_metrics.h" #include "ui/views/context_menu_controller.h" #include "ui/views/controls/textfield/textfield_model.h" #include "ui/views/drag_controller.h" @@ -796,6 +797,9 @@ SelectionDraggingState selection_dragging_state_ = SelectionDraggingState::kNone; + // Tracks the type of the current or pending selection drag gesture. + absl::optional<ui::TouchSelectionDragType> selection_drag_type_; + // The offset applied to the touch drag location when determining selection // updates. gfx::Vector2d selection_dragging_offset_;
diff --git a/ui/views/controls/textfield/textfield_unittest.cc b/ui/views/controls/textfield/textfield_unittest.cc index a8130ac..31b28a8 100644 --- a/ui/views/controls/textfield/textfield_unittest.cc +++ b/ui/views/controls/textfield/textfield_unittest.cc
@@ -18,6 +18,7 @@ #include "base/pickle.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" @@ -47,6 +48,7 @@ #include "ui/gfx/render_text.h" #include "ui/gfx/render_text_test_api.h" #include "ui/strings/grit/ui_strings.h" +#include "ui/touch_selection/touch_selection_metrics.h" #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/border.h" #include "ui/views/controls/textfield/textfield_model.h" @@ -4034,7 +4036,57 @@ EXPECT_EQ(range, gfx::Range(0, 12)); EXPECT_EQ(textfield_->GetSelectedText(), u"Hello string"); } -#endif + +TEST_F(TextfieldTest, TouchSelectionDraggingMetrics) { + base::HistogramTester histogram_tester; + base::test::ScopedFeatureList feature_list; + feature_list.InitWithFeatures( + /*enabled_features=*/{::features::kTouchTextEditingRedesign}, + /*disabled_features=*/{}); + + InitTextfield(); + textfield_->SetText(u"Text in a textfield"); + const gfx::Point kDragStart = views::View::ConvertPointToScreen( + textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); + const gfx::Point kDragEnd = views::View::ConvertPointToScreen( + textfield_, {GetCursorPositionX(10), GetCursorYForTesting()}); + + // Double press drag selection. + event_generator_->GestureTapAt(kDragStart); + event_generator_->GestureScrollSequence(kDragStart, kDragEnd, + /*duration=*/base::Milliseconds(50), + /*steps=*/5); + histogram_tester.ExpectBucketCount( + ui::kTouchSelectionDragTypeHistogramName, + ui::TouchSelectionDragType::kDoublePressDrag, 1); + histogram_tester.ExpectTotalCount(ui::kTouchSelectionDragTypeHistogramName, + 1); + + // Swipe to move cursor. + event_generator_->GestureScrollSequence(kDragStart, kDragEnd, + /*duration=*/base::Milliseconds(50), + /*steps=*/5); + histogram_tester.ExpectBucketCount(ui::kTouchSelectionDragTypeHistogramName, + ui::TouchSelectionDragType::kCursorDrag, + 1); + histogram_tester.ExpectTotalCount(ui::kTouchSelectionDragTypeHistogramName, + 2); + + // Long press drag selection. + event_generator_->PressTouch(kDragStart); + ui::GestureEvent long_press = CreateTestGestureEvent( + kDragStart.x(), kDragStart.y(), + ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS)); + event_generator_->Dispatch(&long_press); + event_generator_->MoveTouchBy(25, 0); + event_generator_->ReleaseTouch(); + histogram_tester.ExpectBucketCount(ui::kTouchSelectionDragTypeHistogramName, + ui::TouchSelectionDragType::kLongPressDrag, + 1); + histogram_tester.ExpectTotalCount(ui::kTouchSelectionDragTypeHistogramName, + 3); +} +#endif // !BUILDFLAG(IS_MAC) TEST_F(TextfieldTest, GetTextfieldBaseline_FontFallbackTest) { InitTextfield();
diff --git a/ui/views/touchui/touch_selection_controller_impl.cc b/ui/views/touchui/touch_selection_controller_impl.cc index c813088..3f38f498 100644 --- a/ui/views/touchui/touch_selection_controller_impl.cc +++ b/ui/views/touchui/touch_selection_controller_impl.cc
@@ -29,6 +29,7 @@ #include "ui/gfx/image/image.h" #include "ui/resources/grit/ui_resources.h" #include "ui/touch_selection/touch_selection_magnifier_aura.h" +#include "ui/touch_selection/touch_selection_metrics.h" #include "ui/touch_selection/vector_icons/vector_icons.h" #include "ui/views/view_utils.h" #include "ui/views/views_delegate.h" @@ -341,6 +342,10 @@ is_dragging_ = false; GetWidget()->ReleaseCapture(); controller_->OnDragEnd(); + ui::RecordTouchSelectionDrag( + is_cursor_handle_ + ? ui::TouchSelectionDragType::kCursorHandleDrag + : ui::TouchSelectionDragType::kSelectionHandleDrag); break; } default:
diff --git a/ui/views/touchui/touch_selection_controller_impl_unittest.cc b/ui/views/touchui/touch_selection_controller_impl_unittest.cc index cbe2c64..e3d34bf4 100644 --- a/ui/views/touchui/touch_selection_controller_impl_unittest.cc +++ b/ui/views/touchui/touch_selection_controller_impl_unittest.cc
@@ -15,6 +15,7 @@ #include "base/memory/raw_ptr.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "base/test/simple_test_tick_clock.h" #include "base/time/time.h" @@ -35,6 +36,7 @@ #include "ui/gfx/geometry/rect.h" #include "ui/gfx/render_text.h" #include "ui/touch_selection/touch_selection_menu_runner.h" +#include "ui/touch_selection/touch_selection_metrics.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/controls/textfield/textfield_test_api.h" #include "ui/views/test/views_test_base.h" @@ -1004,7 +1006,72 @@ EXPECT_FALSE(menu_client->IsCommandIdEnabled(ui::TouchEditable::kSelectAll)); EXPECT_FALSE(menu_client->IsCommandIdEnabled(ui::TouchEditable::kSelectWord)); } -#endif + +TEST_F(TouchSelectionControllerImplTest, CursorHandleDraggingMetrics) { + base::HistogramTester histogram_tester; + base::test::ScopedFeatureList feature_list; + feature_list.InitWithFeatures( + /*enabled_features=*/{::features::kTouchTextEditingRedesign}, + /*disabled_features=*/{}); + + CreateTextfield(); + textfield_->SetText(u"some text in a textfield"); + ui::test::EventGenerator generator( + textfield_->GetWidget()->GetNativeView()->GetRootWindow()); + + // Tap the textfield to make the cursor handle appear. + generator.GestureTapAt(gfx::Point(10, 10)); + EXPECT_TRUE(IsCursorHandleVisible()); + + // Drag the cursor handle. + const gfx::Point drag_start = GetCursorHandleBounds().CenterPoint(); + const gfx::Point drag_end = drag_start + gfx::Vector2d(50, 0); + generator.GestureScrollSequence(drag_start, drag_end, + /*duration=*/base::Milliseconds(50), + /*steps=*/5); + histogram_tester.ExpectBucketCount( + ui::kTouchSelectionDragTypeHistogramName, + ui::TouchSelectionDragType::kCursorHandleDrag, 1); +} + +TEST_F(TouchSelectionControllerImplTest, SelectionHandleDraggingMetrics) { + base::HistogramTester histogram_tester; + base::test::ScopedFeatureList feature_list; + feature_list.InitWithFeatures( + /*enabled_features=*/{::features::kTouchTextEditingRedesign}, + /*disabled_features=*/{}); + + CreateTextfield(); + textfield_->SetText(u"some text in a textfield"); + textfield_->SetSelectedRange(gfx::Range(2, 15)); + ui::test::EventGenerator generator( + textfield_->GetWidget()->GetNativeView()->GetRootWindow()); + + // Tap on the selected text to make selection handles appear. + generator.GestureTapAt(gfx::Point(30, 15)); + EXPECT_TRUE(IsSelectionHandle1Visible()); + + // Drag selection handle 1. + gfx::Point drag_start = GetSelectionHandle1Bounds().CenterPoint(); + gfx::Point drag_end = drag_start + gfx::Vector2d(50, 0); + generator.GestureScrollSequence(drag_start, drag_end, + /*duration=*/base::Milliseconds(50), + /*steps=*/5); + histogram_tester.ExpectBucketCount( + ui::kTouchSelectionDragTypeHistogramName, + ui::TouchSelectionDragType::kSelectionHandleDrag, 1); + + // Drag selection handle 2. + drag_start = GetSelectionHandle2Bounds().CenterPoint(); + drag_end = drag_start + gfx::Vector2d(-60, 0); + generator.GestureScrollSequence(drag_start, drag_end, + /*duration=*/base::Milliseconds(50), + /*steps=*/5); + histogram_tester.ExpectBucketCount( + ui::kTouchSelectionDragTypeHistogramName, + ui::TouchSelectionDragType::kSelectionHandleDrag, 2); +} +#endif // BUILDFLAG(IS_CHROMEOS) // A simple implementation of TouchEditable that allows faking cursor position // inside its boundaries.
diff --git a/ui/views/touchui/touch_selection_menu_runner_views_unittest.cc b/ui/views/touchui/touch_selection_menu_runner_views_unittest.cc index 8aa1d62..bac76864 100644 --- a/ui/views/touchui/touch_selection_menu_runner_views_unittest.cc +++ b/ui/views/touchui/touch_selection_menu_runner_views_unittest.cc
@@ -4,8 +4,11 @@ #include "ui/views/touchui/touch_selection_menu_runner_views.h" +#include "base/test/metrics/histogram_tester.h" #include "ui/events/event_utils.h" +#include "ui/events/test/event_generator.h" #include "ui/touch_selection/touch_selection_menu_runner.h" +#include "ui/touch_selection/touch_selection_metrics.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/test/views_test_base.h" #include "ui/views/touchui/touch_selection_menu_views.h" @@ -218,6 +221,34 @@ RunPendingMessages(); } +// Tests that pressing a menu button records a histogram entry. +TEST_F(TouchSelectionMenuRunnerViewsTest, MenuActionMetrics) { + base::HistogramTester histogram_tester; + TouchSelectionMenuRunnerViews::TestApi test_api( + static_cast<TouchSelectionMenuRunnerViews*>( + ui::TouchSelectionMenuRunner::GetInstance())); + + // Open the menu. + ui::TouchSelectionMenuRunner::GetInstance()->OpenMenu( + GetWeakPtr(), /*anchor_rect=*/gfx::Rect(20, 30), + /*handle_image_size=*/gfx::Size(10, 10), GetContext()); + + EXPECT_TRUE(ui::TouchSelectionMenuRunner::GetInstance()->IsRunning()); + histogram_tester.ExpectTotalCount(ui::kTouchSelectionMenuActionHistogramName, + 0); + + // Tap the first action on the menu. + ui::test::EventGenerator generator( + test_api.GetWidget()->GetNativeView()->GetRootWindow()); + gfx::Point button_center = test_api.GetFirstButton()->bounds().CenterPoint(); + generator.delegate()->ConvertPointFromTarget( + test_api.GetWidget()->GetNativeView(), &button_center); + generator.GestureTapAt(button_center); + + histogram_tester.ExpectTotalCount(ui::kTouchSelectionMenuActionHistogramName, + 1); +} + } // namespace } // namespace views
diff --git a/ui/views/touchui/touch_selection_menu_views.cc b/ui/views/touchui/touch_selection_menu_views.cc index 14b9e20..c10a339 100644 --- a/ui/views/touchui/touch_selection_menu_views.cc +++ b/ui/views/touchui/touch_selection_menu_views.cc
@@ -27,6 +27,7 @@ #include "ui/gfx/text_utils.h" #include "ui/strings/grit/ui_strings.h" #include "ui/touch_selection/touch_selection_menu_runner.h" +#include "ui/touch_selection/touch_selection_metrics.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/layout/box_layout.h" #include "ui/views/views_features.h" @@ -236,6 +237,7 @@ void TouchSelectionMenuViews::ButtonPressed(int command, const ui::Event& event) { + ui::RecordTouchSelectionMenuCommandAction(command); CloseMenu(); if (client_) { client_->ExecuteCommand(command, event.flags()); @@ -243,6 +245,7 @@ } void TouchSelectionMenuViews::EllipsisPressed(const ui::Event& event) { + ui::RecordTouchSelectionMenuEllipsisAction(); CloseMenu(); if (client_) { client_->RunContextMenu();
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm index c7603a6..f7a02135 100644 --- a/ui/views/widget/native_widget_mac_unittest.mm +++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -1426,8 +1426,10 @@ } // Tests behavior of window-modal dialogs, displayed as sheets. -#if defined(ARCH_CPU_ARM64) +#if defined(ARCH_CPU_ARM64) || BUILDFLAG(IS_MAC) // Bulk-disabled as part of arm64 bot stabilization: https://crbug.com/1154345 +// Disabled on Mac 10.15 and 10.11 failing ([parent_close_button isEnabled]) +// crbug.com/1473423 #define MAYBE_WindowModalSheet DISABLED_WindowModalSheet #else #define MAYBE_WindowModalSheet WindowModalSheet
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn index 0f4f4d1..c8b7319f 100644 --- a/weblayer/BUILD.gn +++ b/weblayer/BUILD.gn
@@ -613,8 +613,6 @@ "browser/bluetooth/weblayer_bluetooth_scanning_prompt_android_delegate.h", "browser/browser_list_proxy.cc", "browser/browser_list_proxy.h", - "browser/component_updater/client_side_phishing_component_loader_policy.cc", - "browser/component_updater/client_side_phishing_component_loader_policy.h", "browser/component_updater/registration.cc", "browser/component_updater/registration.h", "browser/content_view_render_view.cc",
diff --git a/weblayer/browser/component_updater/client_side_phishing_component_loader_policy.cc b/weblayer/browser/component_updater/client_side_phishing_component_loader_policy.cc deleted file mode 100644 index 7e76251a..0000000 --- a/weblayer/browser/component_updater/client_side_phishing_component_loader_policy.cc +++ /dev/null
@@ -1,99 +0,0 @@ -// Copyright 2021 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "weblayer/browser/component_updater/client_side_phishing_component_loader_policy.h" - -#include <stdint.h> - -#include <string> -#include <utility> -#include <vector> - -#include "base/check.h" -#include "base/containers/flat_map.h" -#include "base/files/file.h" -#include "base/files/file_util.h" -#include "base/files/scoped_file.h" -#include "base/location.h" -#include "base/task/task_traits.h" -#include "base/task/thread_pool.h" -#include "base/values.h" -#include "base/version.h" -#include "components/component_updater/android/component_loader_policy.h" -#include "components/component_updater/installer_policies/client_side_phishing_component_installer_policy.h" -#include "components/safe_browsing/content/browser/client_side_phishing_model.h" -#include "weblayer/common/features.h" - -namespace weblayer { - -namespace { -// Persisted to logs, should never change. -constexpr char kClientSidePhishingComponentMetricsSuffix[] = - "ClientSidePhishing"; - -void LoadFromDisk(base::ScopedFD pb_fd, base::ScopedFD visual_tflite_model_fd) { - std::string binary_pb; - base::ScopedFILE pb_file_stream( - base::FileToFILE(base::File(std::move(pb_fd)), "r")); - if (!base::ReadStreamToString(pb_file_stream.get(), &binary_pb)) - binary_pb.clear(); - - base::File visual_tflite_model(std::move(visual_tflite_model_fd), - base::File::FLAG_OPEN | base::File::FLAG_READ); - - // The ClientSidePhishingModel singleton will react appropriately if the - // |binary_pb| is empty or |visual_tflite_model| is invalid. - safe_browsing::ClientSidePhishingModel::GetInstance() - ->PopulateFromDynamicUpdate(binary_pb, std::move(visual_tflite_model)); -} - -} // namespace - -void ClientSidePhishingComponentLoaderPolicy::ComponentLoaded( - const base::Version& version, - base::flat_map<std::string, base::ScopedFD>& fd_map, - base::Value::Dict manifest) { - DCHECK(version.IsValid()); - - auto pb_iterator = - fd_map.find(component_updater::kClientModelBinaryPbFileName); - if (pb_iterator == fd_map.end()) - return; - - auto visual_tflite_model_iterator = - fd_map.find(component_updater::kVisualTfLiteModelFileName); - - base::ThreadPool::PostTask( - FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, - base::BindOnce(&LoadFromDisk, std::move(pb_iterator->second), - visual_tflite_model_iterator == fd_map.end() - ? base::ScopedFD() - : std::move(visual_tflite_model_iterator->second))); -} - -void ClientSidePhishingComponentLoaderPolicy::ComponentLoadFailed( - component_updater::ComponentLoadResult /*error*/) {} - -void ClientSidePhishingComponentLoaderPolicy::GetHash( - std::vector<uint8_t>* hash) const { - component_updater::ClientSidePhishingComponentInstallerPolicy::GetPublicHash( - hash); -} - -std::string ClientSidePhishingComponentLoaderPolicy::GetMetricsSuffix() const { - return kClientSidePhishingComponentMetricsSuffix; -} - -void LoadClientSidePhishingComponent( - component_updater::ComponentLoaderPolicyVector& policies) { - if (!base::FeatureList::IsEnabled( - weblayer::features::kWebLayerClientSidePhishingDetection)) { - return; - } - - policies.push_back( - std::make_unique<ClientSidePhishingComponentLoaderPolicy>()); -} - -} // namespace weblayer
diff --git a/weblayer/browser/component_updater/client_side_phishing_component_loader_policy.h b/weblayer/browser/component_updater/client_side_phishing_component_loader_policy.h deleted file mode 100644 index d45b89e..0000000 --- a/weblayer/browser/component_updater/client_side_phishing_component_loader_policy.h +++ /dev/null
@@ -1,51 +0,0 @@ -// Copyright 2021 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef WEBLAYER_BROWSER_COMPONENT_UPDATER_CLIENT_SIDE_PHISHING_COMPONENT_LOADER_POLICY_H_ -#define WEBLAYER_BROWSER_COMPONENT_UPDATER_CLIENT_SIDE_PHISHING_COMPONENT_LOADER_POLICY_H_ - -#include <stdint.h> - -#include <string> -#include <vector> - -#include "base/containers/flat_map.h" -#include "base/files/scoped_file.h" -#include "base/values.h" -#include "components/component_updater/android/component_loader_policy.h" - -namespace base { -class Version; -} // namespace base - -namespace weblayer { - -class ClientSidePhishingComponentLoaderPolicy - : public component_updater::ComponentLoaderPolicy { - public: - ClientSidePhishingComponentLoaderPolicy() = default; - ~ClientSidePhishingComponentLoaderPolicy() override = default; - - ClientSidePhishingComponentLoaderPolicy( - const ClientSidePhishingComponentLoaderPolicy&) = delete; - ClientSidePhishingComponentLoaderPolicy& operator=( - const ClientSidePhishingComponentLoaderPolicy&) = delete; - - private: - // The following methods override ComponentLoaderPolicy. - void ComponentLoaded(const base::Version& version, - base::flat_map<std::string, base::ScopedFD>& fd_map, - base::Value::Dict manifest) override; - void ComponentLoadFailed( - component_updater::ComponentLoadResult error) override; - void GetHash(std::vector<uint8_t>* hash) const override; - std::string GetMetricsSuffix() const override; -}; - -void LoadClientSidePhishingComponent( - component_updater::ComponentLoaderPolicyVector& policies); - -} // namespace weblayer - -#endif // WEBLAYER_BROWSER_COMPONENT_UPDATER_CLIENT_SIDE_PHISHING_COMPONENT_LOADER_POLICY_H_
diff --git a/weblayer/browser/component_updater/registration.cc b/weblayer/browser/component_updater/registration.cc index 0d515d2..d21b275 100644 --- a/weblayer/browser/component_updater/registration.cc +++ b/weblayer/browser/component_updater/registration.cc
@@ -4,14 +4,11 @@ #include "weblayer/browser/component_updater/registration.h" -#include "weblayer/browser/component_updater/client_side_phishing_component_loader_policy.h" - namespace weblayer { component_updater::ComponentLoaderPolicyVector GetComponentLoaderPolicies() { component_updater::ComponentLoaderPolicyVector policies; - LoadClientSidePhishingComponent(policies); // TODO(crbug.com/1233490) register AutoFillRegex component loader policy. return policies;
diff --git a/weblayer/variables.gni b/weblayer/variables.gni index d954ffe..b10d15b5 100644 --- a/weblayer/variables.gni +++ b/weblayer/variables.gni
@@ -6,7 +6,7 @@ declare_args() { # Include the //weblayer code in WebView implementation APKs. - webview_includes_weblayer = true + webview_includes_weblayer = false } weblayer_product_config_java_package = "org.chromium.weblayer_private"